listview滑动源码分析(二)

4, listview滑动刷新

刷新流程如下,

主要是trackMotionScroll方法, 当于手指只要在屏幕上稍微有一点点移动,这个方法就会被调用,而如果是正常在屏幕上滑动的话,那么这个方法就会被调用很多次。

boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
        final int childCount = getChildCount();
        •••
int start = 0;
        int count = 0; // item移动的数量
if (down) { // 手指往上滑动
            int top = -incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                top += listPadding.top;
            }
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getBottom() >= top) {
                    break;
                } else {
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        } else { //手指往下滑动
            int bottom = getHeight() - incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                bottom -= listPadding.bottom;
            }
            for (int i = childCount - 1; i >= 0; i--) {
                final View child = getChildAt(i);
                if (child.getTop() <= bottom) {
                    break;
                } else {
                    start = i;
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        }
••••
if (count > 0) {
            detachViewsFromParent(start, count);
            mRecycler.removeSkippedScrap();
        }
••••
        offsetChildrenTopAndBottom(incrementalDeltaY);

        if (down) {
            mFirstPosition += count;
        }

        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            fillGap(down);
        }
•••
}

1,无论向上还是向下滑动,调用addScrapView方法缓存滑出的View

2,调用offsetChildrenTopAndBottom 方法,将ListView中所有的子View都按照传入的参数值进行相应的偏移,这样就实现了随着手指的拖动,ListView的内容也会随着滚动的效果。

3, 如果ListView中最后一个View的底部已经移入了屏幕,或者ListView中第一个View的顶部移入了屏幕,就会调用fillGap()方法,加载屏幕外数据。

Listview中的fillGap方法源码如下,

void fillGap(boolean down) {
        final int count = getChildCount();
        if (down) { // 向上滑
            int paddingTop = 0;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                paddingTop = getListPaddingTop();
            }
           final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
                    paddingTop;
            fillDown(mFirstPosition + count, startOffset);
            correctTooHigh(getChildCount());
        } else { // 向下滑
            int paddingBottom = 0;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                paddingBottom = getListPaddingBottom();
            }
            final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
                    getHeight() - paddingBottom;
            fillUp(mFirstPosition - 1, startOffset);
            correctTooLow(getChildCount());
        }
    }

fillDown和fillUp方法内部都是通过一个循环调用makeAndAddView方法对ListView进行填充。再次查看makeAndAddView方法,

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

首先仍然是会尝试调用RecycleBin的getActiveView()方法来获取子布局,只不过肯定是获取不到的了,因为在第二次Layout过程中我们已经从mActiveViews中获取过了数据,而根据RecycleBin的机制,mActiveViews是不能够重复利用的,因此这里返回的值肯定是null。所以会调用obtainView方法。

obtainView方法如下,

View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        if (transientView != null) {// 短暂view的缓存,原理和一般缓存一样。
            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

            // If the view type hasn't changed, attempt to re-bind the data.
            if (params.viewType == mAdapter.getItemViewType(position)) {
                final View updatedView = mAdapter.getView(position, transientView, this);

                // If we failed to re-bind the data, scrap the obtained view.
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }

            isScrap[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }

        final View scrapView = mRecycler.getScrapView(position);
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {  
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else {
                isScrap[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            }
        }

        if (mCacheColorHint != 0) {
            child.setDrawingCacheBackgroundColor(mCacheColorHint);
        }

        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        setItemViewLayoutParams(child, position);

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mAccessibilityDelegate == null) {
                mAccessibilityDelegate = new ListItemAccessibilityDelegate();
            }
            if (child.getAccessibilityDelegate() == null) {
                child.setAccessibilityDelegate(mAccessibilityDelegate);
            }
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return child;
    }

1,首先判断是否存在短暂缓存,如果存在,从短暂缓存中获取view

2, 调用getScrapView()方法来尝试从废弃缓存中获取一个View. 在trackMotionScroll()方法中我们就已经看到了,一旦有任何子View被移出了屏幕,就会将它加入到废弃缓存中,而从obtainView()方法中的逻辑来看,一旦有新的数据需要显示到屏幕上,就会尝试从废弃缓存中获取View。所以它们之间就形成了一个生产者和消费者的模式,那么ListView神奇的地方也就在这里体现出来了,不管你有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据重新利用起来,因而不管加载多少数据都不会出现OOM的情况,甚至内存都不会有所增加。

public View getView(final int position, View view, ViewGroup arg2) {

                 ViewHolder viewHolder = null;

                 final SortModel mContent = list.get(position);

                 if (view == null) {

                          viewHolder = newViewHolder();

                          view =LayoutInflater.from(mContext).inflate(R.layout.item, null);

                           viewHolder.tvTitle =(TextView) view.findViewById(R.id.title);

                          viewHolder.tvLetter =(TextView) view.findViewById(R.id.catalog);

                           viewHolder.tvnumber =(TextView) view.findViewById(R.id.number);

                           view.setTag(viewHolder);

                 } else {

                           viewHolder =(ViewHolder) view.getTag();

                 }

                

                 int section =getSectionForPosition(position);

                 if(position ==getPositionForSection(section)){

                           viewHolder.tvLetter.setVisibility(View.VISIBLE);

                           viewHolder.tvLetter.setText(mContent.getSortLetters());

                 }else{

                           viewHolder.tvLetter.setVisibility(View.GONE);

                 }

       

                 viewHolder.tvTitle.setText(this.list.get(position).getName());

                 viewHolder.tvnumber.setText(this.list.get(position).getNumber());

                 return view;

        }

在写getView()方法是要判断一下view是不是等于null,如果等于null才调用inflate()方法来加载布局,不等于null就可以直接利用view,因为该view就是我们之间利用过的子View,只不过被移出屏幕后进入到了废弃缓存中,现在又重新拿出来使用而已。只需要把view中的数据更新成当前位置上应该显示的数据,那么看起来就好像是全新加载出来的一个布局一样,大大提高了效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现ListView滑动删除的功能,可以采用以下步骤: 1. 首先创建一个ListView,并给ListView设置一个适配器; 2. 在适配器的getView()方法中,为每一个item添加一个滑动删除的功能; 3. 在item中添加一个布局,该布局包含两个子控件:一个是要显示的内容,另一个是删除按钮; 4. 给删除按钮添加点击事件,当点击删除按钮时,将该item从ListView中移除; 5. 在ListView的OnTouchListener中,监听手势滑动事件,并实现滑动删除的效果。 下面是一个简单的实现代码: ``` public class MyAdapter extends BaseAdapter implements View.OnTouchListener { private Context mContext; private List<String> mData; private int mLastPosition; private int mDownX, mDownY; private boolean isSlide = false; public MyAdapter(Context context, List<String> data) { mContext = context; mData = data; } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.item_layout, null); viewHolder = new ViewHolder(); viewHolder.contentTv = convertView.findViewById(R.id.content_tv); viewHolder.deleteBtn = convertView.findViewById(R.id.delete_btn); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.contentTv.setText(mData.get(position)); viewHolder.deleteBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mData.remove(position); notifyDataSetInvalidated(); } }); convertView.setOnTouchListener(this); convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 点击事件 } }); return convertView; } static class ViewHolder { TextView contentTv; Button deleteBtn; } @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX(); mDownY = (int) event.getY(); mLastPosition = v.getId(); isSlide = false; break; case MotionEvent.ACTION_MOVE: int moveX = (int) event.getX(); int moveY = (int) event.getY(); int deltaX = moveX - mDownX; int deltaY = moveY - mDownY; if (Math.abs(deltaX) > Math.abs(deltaY)) { isSlide = true; // 滑动事件 } break; case MotionEvent.ACTION_UP: if (!isSlide) { // 点击事件 } break; } return true; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值