ShowcaseView - width and height must be > 0

2019-01-20 13:50发布

问题:

Research has been fruitless because all other references to this error seem to be reliably repeatable; I'm unable to consistently repeat this error:

08-22 17:32:25.216  22699-22699/com.smbds.punisher11 E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.IllegalArgumentException: width and height must be > 0
            at android.graphics.Bitmap.createBitmap(Bitmap.java:1023)
            at android.graphics.Bitmap.createBitmap(Bitmap.java:1002)
            at android.graphics.Bitmap.createBitmap(Bitmap.java:985)
            at com.github.amlcurran.showcaseview.ShowcaseView.updateBitmap(ShowcaseView.java:171)
            at com.github.amlcurran.showcaseview.ShowcaseView.onGlobalLayout(ShowcaseView.java:354)
            at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:655)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2054)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1190)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4860)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:766)
            at android.view.Choreographer.doCallbacks(Choreographer.java:575)
            at android.view.Choreographer.doFrame(Choreographer.java:542)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:751)
            at android.os.Handler.handleCallback(Handler.java:725)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:158)
            at android.app.ActivityThread.main(ActivityThread.java:5751)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1083)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:850)
            at dalvik.system.NativeStart.main(Native Method)

As I stated above, this error does not occur all the time. It seems to happen about (but not exactly) 2-3 times, then 2-3 runs go through without a problem.

Here is my resources file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container_fragment_person_list"
    android:tag="PersonListFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragments.PersonListFragment">

    <TextView
        android:id="@+id/textView_add_guardian_placeholder"
        android:layout_centerInParent="true"
        android:layout_width="1dp"
        android:layout_height="1dp" />

    <TextView
        android:id="@+id/textView_intro_placeholder"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:minWidth="1dp"
        android:minHeight="1dp"
        android:layout_width="1dp"
        android:layout_height="1dp" />

    <ListView
        android:visibility="gone"
        android:id="@id/android:list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:orientation="vertical"
        android:id="@id/android:empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center" >

        <TextView
            android:text="@string/no_people"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <ImageButton
            android:id="@+id/imageButton_empty_text"
            android:src="@drawable/ic_man_silhouette_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@string/generic_content_description"/>

        <TextView
            android:id="@+id/test"
            android:text="@string/tap_icon_to_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</RelativeLayout>

And here is my java. Please notice that I even tried hard-coding a width and height for my ViewTarget, but still get inconsistent errors. I also tried creating placeholder TextViews in my resources file, but still no go.

public class PersonListFragment extends BaseListFragment {

    private PersonAdapter personAdapter;
    private PersonModel personModel;
    private int personCount;
    private TextView tvTargetIntro;

    public static PersonListFragment newInstance(String tag, Long index) {
        PersonListFragment fragment = new PersonListFragment();
        Bundle args = new Bundle();
        args.putString(TAG, tag);
        args.putLong(INDEX, index);
        fragment.setArguments(args);
        return fragment;
    }

    public PersonListFragment() {
    }

    @SuppressWarnings("UnusedDeclaration")
    public void onEventMainThread(People_FetchedEvent ignored) {
        refreshList();
    }

    @SuppressWarnings("UnusedDeclaration")
    public void onEventMainThread(Person_CreatedEvent ignored) {
        String createType;
        if(ignored.getId() == 0 | ignored.getPerson().getId() == null) {
            createType = resources.getString(R.string.added);
        } else {
            createType = resources.getString(R.string.updated);
        }
        MessageManager.getInstance().makeToast(getActivity(), ignored.getPerson().getName(), " ", createType);
        fm.popBackStack();
        refreshList();
    }

    @SuppressWarnings("UnusedDeclaration")
    public void onEventMainThread(Person_BuiltEvent ignored) {
        makeCreatePersonBackgroundTask(ignored.getPerson());
    }

    @SuppressWarnings("UnusedDeclaration")
    public void onEventMainThread(Person_DeletingEvent ignored) {
        personModel.deletePersonByLocalId(ignored.getId());
        MessageManager.getInstance().makeToast(getActivity(), PersonController.getInstance().getCurrentPerson().getName() + " " + resources.getString(R.string.removed));
        refreshList();
        FragmentManager fm = getChildFragmentManager();
        fm.popBackStack();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        personModel = PersonModel.getInstance();
        personAdapter = new PersonAdapter(getActivity().getLayoutInflater(), getActivity(), ListType.PROFILE_INFO);
        setListAdapter(personAdapter);
        personCount = PersonModel.getInstance().getPersonCount();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_person_list, container, false);

        LinearLayout layoutEmptyList = (LinearLayout) view.findViewById(android.R.id.empty);
        tvTargetIntro = (TextView) view.findViewById(R.id.textView_intro_placeholder);
        ImageButton imageButtonEmptyText = (ImageButton) view.findViewById(R.id.imageButton_empty_text);
        imageButtonEmptyText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction ft;
                ft = fm.beginTransaction();
                ft.replace(R.id.container_fragment_person_list, PersonEditFragment.newInstance("PERSON_EDIT_FRAGMENT", (long) 0, new Person()), "PERSON_EDIT_FRAGMENT");
                ft.addToBackStack(null);
                ft.commit();
                fm.executePendingTransactions();
            }
        });


        switch(personCount) {
            case 0:
                break;
            case 1:
                break;
            default:
        }

        return view;
    }

    private void makeShowCaseIntro() {
        ViewTarget target1 = new ViewTarget(tvTargetIntro);
        tvTargetIntro.setWidth(1);
        tvTargetIntro.setHeight(1);
        ShowcaseView sv = new ShowcaseView.Builder(getActivity())
                .setTarget(target1)
                .setContentTitle(resources.getString(R.string.showcase_intro_title))
                .setContentText(resources.getString(R.string.showcase_intro_text))
                .setStyle(R.style.CustomShowcaseTheme2)
                .build();
        sv.setButtonText(resources.getString(R.string.next));
        sv.show();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        makeShowCaseIntro();

    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        PersonController.getInstance().setCurrentPerson(personAdapter.getItem(position));

        FragmentTransaction ft = fm.beginTransaction();

        ft.replace(R.id.container_fragment_person_list, PersonDisplayFragment.newInstance("PERSON_DISPLAY_FRAGMENT", PersonController.getInstance().getLocalId(), PersonController.getInstance().getCurrentPerson()), "PERSON_DISPLAY_FRAGMENT");
        ft.addToBackStack(null);
        ft.commit();
        fm.executePendingTransactions();
    }

    private void refreshList() {
        new SimpleBackgroundTask<LazyList<Person>>(getActivity()) {

            @Override
            protected LazyList<Person> onRun() {
                return PersonModel.getInstance().lazyLoadPeople();
            }

            @Override
            protected void onSuccess(LazyList<Person> result) {
                personAdapter.replaceLazyList(result);
            }
        }.execute();
    }

    private void makeCreatePersonBackgroundTask(Person person) {
        jobManager.addJobInBackground(new CreatePersonJob(person));
        fm.popBackStack();
    }

    @Override
    public void onResume() {
        super.onResume();
        jobManager.addJobInBackground(new FetchPeopleJob());
        if(dataDirty) {
            refreshList();
            dataDirty = false;
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        FragmentTransaction ft;
        Person person = PersonController.getInstance().getCurrentPerson();
        ft = fm.beginTransaction();
        switch(item.getItemId()) {
            case R.id.action_new:
                ft.replace(R.id.container_fragment_person_list, PersonEditFragment.newInstance("PERSON_EDIT_FRAGMENT", (long) 0, new Person()), "PERSON_EDIT_FRAGMENT");
                ft.addToBackStack(null);
                ft.commit();
                fm.executePendingTransactions();
                return true;
            case R.id.action_edit:
                ft.replace(R.id.container_fragment_person_list, PersonEditFragment.newInstance("PERSON_EDIT_FRAGMENT", person.getId(), person), "PERSON_EDIT_FRAGMENT");
                ft.addToBackStack(null);
                ft.commit();
                fm.executePendingTransactions();
                return true;
            case R.id.action_remove:
                String title = MessageManager.getInstance().makeString(resources.getString(R.string.remove), " ", person.getName());
                String message = MessageManager.getInstance().makeString(resources.getString(R.string.message_confirm_remove), " ", person.getName(), resources.getString(R.string.punctuation_question));
                final InfoDialogFragment dialog = InfoDialogFragment.newInstance(title, message, DialogActionType.Delete, DialogActionTarget.Person, person.getId());
                dialog.show(ft, DIALOG_TAG);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

}

I'm pretty sure this is some timing issue with the way android draws out objects, but I don't have the knowledge to know why they're drawn correctly on some runs but not on others.

Please don't zap this question unless you're sure that it has been asked elsewhere with respect to an intermittent problem :-)

Any help is appreciated.

回答1:

This is a known issue : Github issues when updateBitmap() is called too early.
This issue comes from Bitmap.createBitmap() method either width or height equals to 0.
A workaround is to delay the ShowcaseView creation more:

someView.post(new Runnable() {
    @Override
    public void run() {
        // display ShowcaseView here
    }
});

You could also change the updateBitmap() method from the library to not create the bitmap if the view width or height is wrong.



回答2:

Had the same problem, and posted my solution in another thread: https://stackoverflow.com/a/25521608/1752670

I changed the ShowcaseView class slightly



回答3:

go to com.github.amlcurran.showcaseview.ShowcaseView and change this method

private void updateBitmap() {
        if ((bitmapBuffer == null || haveBoundsChanged()) && getMeasuredHeight() > 0 && getMeasuredWidth() > 0) {
            if(bitmapBuffer != null)
                bitmapBuffer.recycle();
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            if(width > 0 && height > 0)
                bitmapBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        }
    }


回答4:

This was a bug in ShowcaseView, and was fixed starting in v5.4.2: https://github.com/amlcurran/ShowcaseView/commit/c79c60dc798f081fb62e48c549c7bdbff8da51b9

So, you can just update your build.gradle to:

compile 'com.github.amlcurran.showcaseview:library:5.4.2'