Question

I have some code here (which I did not write) which I need to fix. Here's the required flow:

  1. User clicks on an item in a ListView
  2. The item expands to show a footer which is otherwise hidden
  3. If another list item is expanded, it is shrunk back to normal size (so that only 1 item is expanded at a time).

My problem: When tapping an item which is not expanded, nothing happens. The 2nd time, the item expands, tapping again shrinks it, then once again the 1st tap does nothing and so on.

Of course, I'm trying to eliminate the 1st redundant tap which does nothing.

Another interesting side-effect: When I tap an item the 1st time, nothing happens, then I will tap a DIFFERENT item once, and both the items will expand together.

I've been over the code for quite a while now and I can't see what's causing this.

Here's the code:

Setting the listener on the ListView:

productsListView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> list, View view,int position, long id) 
            {


                if (lastSelectedPosition == -1) {
                    lastSelectedPosition = position;
                } else if (lastSelectedPosition == position) {
                    lastSelectedPosition = -1;
                } else {
                    lastSelectedPosition = position;
                }


                View child;
                ProductItemView tag;
                for (int i = 0; i < productsListView.getChildCount(); i++) {
                    child = productsListView.getChildAt(i);
                    tag = (ProductItemView) child.getTag();
                    tag.onSomeListItemClicked(position);
                    productsListView.smoothScrollToPosition(position);
                }


            }
        });

The list view's adapter:

public class ProductsCursorAdapter extends CursorAdapter {

        public ProductsCursorAdapter(Context context, Cursor c, int flags) {
            super(context, c, flags);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            ProductItemView item = null;

            int pos = cursor.getPosition();
            Log.d("BookListFragment", "BookListFragment: Position is: " + pos);
            item = new ProductItemView(getActivity(), cursor.getPosition(), view, new ProductDAO(cursor));

            view.setTag(item);

            item.setContainer(BookListFragment.this, BookListFragment.this);

            if (lastSelectedPosition == cursor.getPosition()) {
                item.openedFooter();
            }

        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
            View view = (View) getActivity().getLayoutInflater().inflate(R.layout.course_list_item, null);

            return view;

        }
    }

Relevant code inside ProductItemView:

public void onSomeListItemClicked(int position) 
    {
        if (m_position == position)
        {
            Log.i("ProductItemView", "Animate footer for position: " + m_position);
            animateFooter(position);
        }
        else
        {

            Log.i("ProductItemView", "Hide footer for position: " + m_position);
            hideFooter(position);

        }
    }

public void showFooter(int position) {

        if (!isFooterVisible())
        {
            animateFooter(position);
        }
    }

    public void hideFooter(int position) 
    {
        Log.i("ProductItemView", "Hide called for position: " + m_position);

        if (isFooterVisible() && position != m_position)
        {
            animateFooter(position);
        }

    }

    public void animateFooter(final int position) 
    {
        if (footer != null && (m_footerExpandAnim == null || m_footerExpandAnim.hasEnded()))
        {
            Log.i("ProductItemView", "Animating footer for position: " + m_position);
            isFooterVisible=!isFooterVisible;


            m_footerExpandAnim = new ExpandAnimation(footer, 200, animationDelegate, position);
            footer.startAnimation(m_footerExpandAnim);      
        }
    }

ExpandAnimation:

public ExpandAnimation(View view, int duration, AnimationDelegate delegate, int position) {

        this.position = position;
        this.delegate = delegate;
        setDuration(duration);
        mAnimatedView = view;
        mViewLayoutParams = (LayoutParams) view.getLayoutParams();

        // decide to show or hide the view
        mIsVisibleAfter = (view.getVisibility() == View.VISIBLE);


        mMarginStart = mViewLayoutParams.bottomMargin;


        mMarginEnd = (mMarginStart == 0 ? (-view.getHeight()) : 0);
        mAnimatedView.clearAnimation();

        Log.i("ExpandAnimation", "Margin Start = " + mMarginStart + ", Margin End = " + mMarginEnd);
        //view.setVisibility(View.VISIBLE);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);

        Log.i("ExpandAnimation", "InterpolatedTime: " + interpolatedTime);

        if (interpolatedTime < 1.0f) {

            // Calculating the new bottom margin, and setting it
            mViewLayoutParams.bottomMargin = mMarginStart
                    + (int) ((float)(mMarginEnd - mMarginStart) * interpolatedTime);

            mAnimatedView.setLayoutParams(mViewLayoutParams);
            // Invalidating the layout, making us seeing the changes we made
            mAnimatedView.requestLayout();
            mAnimatedView.postInvalidate();

        // Making sure we didn't run the ending before (it happens!)
        } else if (!mWasEndedAlready) {
            mViewLayoutParams.bottomMargin = mMarginEnd;
            mAnimatedView.setLayoutParams(mViewLayoutParams);
            mAnimatedView.requestLayout();
            mAnimatedView.postInvalidate();

            if (mIsVisibleAfter) {
                //mAnimatedView.setVisibility(View.GONE);
            }
            mWasEndedAlready = true;
        }

        if(delegate!=null){
            delegate.animationDidEnd(position);
        }
    }

Some things I've noticed:

  1. The 1st time the item is clicked, the ExpandAnimation's constructor is indeed called, but the logs from the applyTransformation method aren't printed.

  2. The 2nd time the item is clicked, the ExpandAnimation's constructor is called, but the mMarginStart value is not what it should be (randomly between -60 to -80 instead of -100), but then the logs in the applyTransformation are printed properly.

If you need any more code, let me know. Any ideas would help.

As I mentioned, this is not my code - I'm trying to edit code which a developer who has since left wrote. If it were up to me, this entire thing would'v been written very differently. I require a solution which involves minimal changes to the code structure.

Was it helpful?

Solution

Okay, I found the problem.

The clue was that I noticed that after the 1st click which "did nothing", if I scrolled the list slightly, the item I clicked would suddenly expand. This told me that the ListView was, for some reason, preventing its child views from performing UI operations.

I added a postInvalidate call on the list on the OnItemClick listener, and everything works as expected.

Interesting.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top