Android中的事件冲突问题及解决方案

ScrollView 嵌套 ScrollView 的滑动冲突

当我们在xml布局文件中使用 ScrollView 嵌套 ScrollView 时,会出现这样的问题:
外层的 ScrollView 能够正常滑动,但是内部的 ScrollView 却滑动不了。

查看 ScrollView 的源码发现

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
	/*
	 * This method JUST determines whether we want to intercept the motion.
	 * If we return true, onMotionEvent will be called and we do the actual
	 * scrolling there.
	 */

	/*
	* Shortcut the most recurring case: the user is in the dragging
	* state and they is moving their finger.  We want to intercept this
	* motion.
	*/
    final int action = ev.getAction();
    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
        return true;
    }

    if (super.onInterceptTouchEvent(ev)) {
        return true;
    }

    /*
	 * Don't try to intercept touch if we can't scroll anyway.
	 */
    if (getScrollY() == 0 && !canScrollVertically(1)) {
        return false;
    }
    ...
    ...
    ...

在 onInterceptTouchEvent 方法中,首先就判断了 ev.getAction() 是不是 ACTION_MOVE,
当我们滑动的时候传进来的 MotionEvent.getAction 就是 ACTION_MOVE,此时返回 true 之后,直接拦截。那么接下来他的子 View 就接收不了 event,event 被传入他自己的onTouchEvent 中去进行滚动操作了。

所以,我们内部的 ScrollView 滑动没有响应的原因就是,那时候手指是在滑动的,一直不断传入 ACTION_MOVE,所以 event 一直被外部的 ScrollView 在上述的操作中拦截了。

就是说只要你手指在 ScrollView 上滑动,ScrollView 内部的子 View 就永远接收不到任何事件,就是永远无响应。

解决方案

在自定义控件中重写 onInterceptTouchEvent 告诉所有父 View:不要拦截事件

public class MyScrollView extends ScrollView {

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        // 请求父类不拦截 TouchEvent
        getParent().requestDisallowInterceptTouchEvent(true);

        return super.onInterceptTouchEvent(ev);
    }
}

getParent().requestDisallowInterceptTouchEvent(true);

意思就是取得父类,然后请求父类不拦截 TouchEvent 的意思。

重写完成之后 buildProject,最后在 xml 布局文件中将内部的 ScrollView 修改成自定义的MyScrollView,内部的 ScrollView 就能够正常滑动了。


ListView 嵌套 CheckBox 选中状态错乱问题

我们知道,ListView 使用的是复用机制

ListView 复用的原理:ListView中的每一个 Item 显示都需要 Adapter 调用一次 getView 的方法,这个方法会传入一个 convertView 的参数,返回的 View 就是这个 Item 显示的 View。如果当 Item 的数量足够大,再为每一个 Item 都创建一个 View 对象,必将占用很多内存,创建 View(mInflater.inflate(R.layout.lv_item, null); 从 xml 中生成 View,这是属于IO操作)也是耗时操作,所以必将影响性能。

Android提供了一个叫做 Recycler(反复循环器)的构件,就是当 ListView 的 Item 从上方滚出屏幕视角之外,对应 Item 的 View 会被缓存到 Recycler 中,相应的会从下方生成一个 Item,而此时调用的 getView 中的 convertView 参数就是滚出屏幕的 Item 的 View,所以说如果能重用这个 convertView,就会大大改善性能。

如果一个屏幕最多显示7个 Item,当 Item1 滑出屏幕,此时 Item1 的 View 被添加进 Recycler 中,相应的在下部要产生一个 Item8,这时调用 getView 方法,convertView 参数就是 Item1 的View。

ListView 的复用虽然大大提升了性能,但是却也带来很多问题。比如在加载图片时,由于下边的 item 复用了上边的 item,造成下边 item 刚加载出来时显示的还是上边被复用的 item 的图片,等到这个新的 item 加载图片完毕时才会正常显示,这就是 convert view 复用造成 listview图片加载错乱的问题。

与上边问题相似,在 listview 的 item 中存在 CheckBox 时也会由于复用 convert view 导致 CheckBox 的选中状态错乱,本片内容将解决由于复用导致 CheckBox 选中状态错乱的问题。

解决方案

在 Adapter 中加入一个Map集合,其中 Map 的 key 用来存储被选中的 checkbox 的 position,value 用来存储 checkbox 是否被选中。代码中还添加了 checkbox 的监听事件,在监听事件中判断点击的 checkbox 是否被选中,如果被选中了则将 position 添加到集合,并设置状态未 true,否则就将该 checkbox 从集合中移除。然后通过 if 语句判断集合中是否存在该 checkbox,如果存在则证明是被选中的,就将该 checkbox 设置为选中状态 setChecked(true),否则证明 checkbox 没有选中则设置 setChecked(false)。这样就解决了checkbox选中状态错乱的问题。

/**
 * @author xinl5457
 * @date 2021/10/19
 */
public class MyAdapter extends BaseAdapter {

    private List<String> mStringList;
    private Context mContext;

    /**
     * 存放已被选中的 CheckBox
     */
    private Map<Integer,Boolean> map = new HashMap<>();

    public MyAdapter(List<String> mStringList, Context mContext) {
        this.mStringList = mStringList;
        this.mContext = mContext;
    }

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

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        MyViewHolder holder;
        if (convertView == null) {
            convertView = View.inflate(mContext, R.layout.list_item,null);
            holder = new MyViewHolder();
            holder.tvCity = convertView.findViewById(R.id.tv_city);
            holder.cb = convertView.findViewById(R.id.cb);
            convertView.setTag(holder);
        } else {
            holder = (MyViewHolder) convertView.getTag();
        }

        holder.tvCity.setText(mStringList.get(position));

        // 监听 CheckBox 的选中状态
        holder.cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    map.put(position,true);
                } else {
                    map.remove(position);
                }
            }
        });

        if (map != null && map.containsKey(position)) {
            holder.cb.setChecked(true);
        } else {
            holder.cb.setChecked(false);
        }

        return convertView;
    }

    public static class MyViewHolder{
        TextView tvCity;
        CheckBox cb;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值