如何判断ListView的某个条目是否滑出了屏幕

public class MainActivity extends AppCompatActivity {
    private List<String> data = new ArrayList<>();
    private ListView listView;
    private int mPosition;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.listview);
        initData();
        ListAdapter adapter = new ListAdapter(this, data);
        listView.setAdapter(adapter);
    }

    public void initData() {
        for (char i = 'A'; i < 'Z'; i++) {
            data.add((Character) (i) + "");
        }
    }

    public void update(View view) {
        updateItemWithPosition(3);
    }

    //更新指定位置的条目
    public void updateItemWithPosition(int position) {
        mPosition = position;
    }

    class ListAdapter extends BaseAdapter {
        private Context mContext;
        private List<String> mData;

        ListAdapter(Context context, List<String> data) {
            this.mContext = context;
            this.mData = data;
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public String getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = View.inflate(mContext, R.layout.list_item_layout, parent);
                holder.textView = convertView.findViewById(R.id.textview);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.textView.setText(getItem(position));
            return convertView;
        }

        class ViewHolder {
            private TextView textView;
        }
    }

运行上面的代码,我们会得到下面的崩溃信息

 android.view.InflateException: Binary XML file line #11: addView(View, LayoutParams) is not supported in AdapterView
                                                                         at android.view.LayoutInflater.inflate(LayoutInflater.java:539)
                                                                         at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
                                                                         at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
原因就是我们填充布局时出现了错误
convertView = View.inflate(mContext, R.layout.list_item_layout, parent);

那么正确的方式怎么写呢,

convertView = mInflater.inflate(R.layout.list_item_layout,parent,false);

假如我们的parent传入了null,那么你在条目跟布局上设置的layoutParam属性就不管用了,我们可以从源码的角度来看看

  if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

这里先判断root是否为null,不为null的话,先构造layoutParam属性,然后判断attachToRoot的值,attachToRoot为true代表将这个布局添加到root布局,并返回root布局,为false代表不会添加到root布局,返回resource指定的布局。


完整代码

public class MainActivity extends AppCompatActivity {
    private List<String> data = new ArrayList<>();
    private ListView listView;
    private int mPosition;
    private int mLastVisiablePosition;
    private int mFirstVisiablePosition;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.listview);
        initData();
        ListAdapter adapter = new ListAdapter(this, data);
        listView.setAdapter(adapter);
        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                mFirstVisiablePosition = firstVisibleItem;
                listView.post(new Runnable() {
                    @Override
                    public void run() {
                        //防止listview没初始化好时getLastVisiblePosition返回1
                        mLastVisiablePosition = listView.getLastVisiblePosition();
                    }
                });

                System.out.println("listview: " + firstVisibleItem+"--"+mLastVisiablePosition+"--"+ mPosition);
                //判断点击的Item是否在屏幕内,判断的条件是点击Item的position大于屏幕内
                //第一个可见的position并且小于最后一个可见的position
                if (firstVisibleItem <= mPosition && mPosition <= mLastVisiablePosition) {
                    System.out.println("listview: 屏幕内" );
                }
            }
        });
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(getBaseContext(),""+position,Toast.LENGTH_SHORT).show();
            }
        });
    }

    public void initData() {
        for (char i = 'A'; i < 'Z'; i++) {
            data.add((Character) (i) + "");
        }
    }

    //更新指定位置的条目
    public void updateItemWithPosition(int position) {
        mPosition = position;
        //因为getChildAt中的position指的是可见区域的第几个元素,这里要减去屏幕上第一个可见元素的位置
        // listView.getChildCount()得到的是可见区域内元素个数
        View itemView = listView.getChildAt(position - mFirstVisiablePosition);
        ListAdapter.ViewHolder holder = (ListAdapter.ViewHolder) itemView.getTag();
        holder.button.setText("已更新");
        holder.button.setEnabled(false);
        //点击button的时候,与当前的position进行关联
        holder.button.setTag(position);
    }

    class ListAdapter extends BaseAdapter {
        private List<String> mData;
        private LayoutInflater mInflater;

        ListAdapter(Context context, List<String> data) {
            this.mData = data;
            mInflater = LayoutInflater.from(context);
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public String 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 holder;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.list_item_layout, parent, false);
                holder.textView = convertView.findViewById(R.id.textview);
                holder.button = convertView.findViewById(R.id.button);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.textView.setText(getItem(position));
            //防止复用导致的显示错误
            if (holder.button.getTag() != null && holder.button.getTag().equals(position)) {
                holder.button.setEnabled(false);
            } else {
                holder.button.setEnabled(true);
                holder.button.setText("更新");
            }
            holder.button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    updateItemWithPosition(position);
                }
            });
            return convertView;
        }

        class ViewHolder {
            private TextView textView;
            private Button button;
        }
    }
}

我们可以看到ListView的Item布局中有个Button,如果我们不进行处理的话会导致ListView的item点击事件失效,解决办法是

在ListView的Item的跟布局加上,意思是

 android:descendantFocusability="blocksDescendants"

意思是ViewGroup会覆盖子类而直接获得焦点

View的post问题

上面的代码中我们为了防止一开始获取ListView的最后一个可见条目位置不正确,我们调用了它的post方法,接下来我们看看post方法是如何做到的,post方法是定义在View中的

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

首先判断attachInfo是否为null,不为null,然后调用Handler的post方法,这个没什么说的,我们主要看看attachInfo是何时初始化的

public ViewRootImpl(Context context, Display display) {
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);

通过查看源码,我们发现是在ViewRootImpl初始化的时候被创建的,接下来我们看看ViewRootImpl是何时初始化的,同样在源码中可以找到答案,在ActivityThread的handleResumeActivity方法中,这个方法主要是回调Activity的onResume方法,在这个方法中有这样的代码

 r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

最后调用了wm.addView方法,ViewManager是一个接口,主要用来为Activity添加和删除View,定义如下

public interface ViewManager{

        public void addView(View view, ViewGroup.LayoutParams params);

        public void updateViewLayout(View view, ViewGroup.LayoutParams params);

        public void removeView(View view);
}

它的实现类是WindowManager,而WindowManager的实现类是WindowManagerImpl,上面其实是调用了WindowManagerImpl的addView方法

@Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

mGlobal是WindowManagerGlobal的对象,最终我们发现了

  root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);

综上所述,attachInfo是在handleResume方法中被创建的,也就是说在Activity的onResume执行之前,attachInfo还没初始化,然后我们回到View的post方法,接着调用了

 getRunQueue().post(action);
private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

这里我们获取到了一个HandlerActionQueue方法,这是一个队列,这个队列其实保存了当前View的需要执行的runnable任务,主要用处是当当前View的handler对象没有关联上的时候,先把任务保存起来,然后延迟执行,那么这个执行的时机是什么呢,接下来我们可以看到答案,我们先看HandlerActionQueue的具体实现

public class HandlerActionQueue {
    private HandlerAction[] mActions;//任务数组
    private int mCount;//任务总个数

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);//将提交的任务保存到任务数组里
            mCount++;
        }
    }

    public void removeCallbacks(Runnable action) {
        synchronized (this) {
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
                if (actions[i].matches(action)) {
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }

                if (j != i) {
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }

                j++;
            }

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }
    //执行任务数组里的任务
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

    public int size() {
        return mCount;
    }

    public Runnable getRunnable(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].action;
    }

    public long getDelay(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].delay;
    }
    //人物对象
    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}

我们通过post提交的任务保存在了mActions数组里,接下来我们重点看看这些任务是在何时执行的,也就是说executeActions方法是在哪里调用的,在ViewRootImpl的performTraversals里我们找到了

private void performTraversals() {
     // cache mView since it is used so much below...
     final View host = mView;
       host.dispatchAttachedToWindow(mAttachInfo, 0);

这个mView就是当前的View,我们看看它具体是在哪里赋值的

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

在setView方法里进行了初始化,setView调用的时机我们从上面的代码可以看出,是在WindowManagerGlobal的addView方法中调用的。然后我们看看View的dispatchAttachedToWindow方法

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
       ...。
        // Transfer all pending runnables.
        if (mRunQueue != null) {//执行通过post方法保存到任务队列中的任务
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
综上所属,我们通过post方法提交单任务队列中的任务,是在performTraversals方法中执行任务的,performTraversals中开始了View的绘制,所以说,View的post方法提交的任务是在View下次开始绘制的时候执行的。













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
判断 Android 中的 ListView 是否处于滑动状态,你可以通过监听 ListView 的滚动事件来实现。下面是一种常见的方法: 1. 首先,在你的 Activity 或 Fragment 中找到你的 ListView,并为它设置一个滚动监听器: ```java ListView listView = findViewById(R.id.listView); listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 在滚动状态改变时调用 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { // ListView 停止滚动 // 这里可以执行相关操作 } else if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { // ListView 正在被触摸滚动 // 这里可以执行相关操作 } else if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { // ListView 正在惯性滚动 // 这里可以执行相关操作 } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 滚动时调用,可以不处理 } }); ``` 2. 在滚动状态改变时,通过 `scrollState` 参数的值来判断 ListView 的滚动状态。在 `onScrollStateChanged()` 方法中,通过比较 `scrollState` 的值,可以判断ListView 是否处于滑动状态。 - `SCROLL_STATE_IDLE`:表示 ListView 停止滚动。 - `SCROLL_STATE_TOUCH_SCROLL`:表示 ListView 正在被触摸滚动。 - `SCROLL_STATE_FLING`:表示 ListView 正在惯性滚动。 你可以根据具体的需求,在相应的条件下执行相关的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值