• 欢迎访问我的博客,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站
  • 本网站关闭了评论功能,联系请点击→邮箱
  • Ctrl+D 可快捷收藏本站点

关于Android的SwitchButton的一些坑

andriod开发 gql 3年前 (2021-10-04) 1466次浏览

一 、先理需求

网格布局管理内放设备块,每个设备块内部有一些控件来展示设备的相关信息,有一个SwitchButton来控制设备的开关。点击/滑动开关到相应的状态时,发送指令到服务器并等待反馈,再更新UI。如下图所示当开关关闭时,图标灰显。

当开关打开时图标呈现彩色

 

二、开始实现,慢慢入坑

由于用到了RecyclerView,所以我们的设备的容器继承RecyclerView.Adapter。设置一个点击SwitchButton事件的监听。在Fragment监听到单击后开始执行发送消息的任务到服务器,并等待。更新设备的状态标志位,不更新UI。然后在onBindViewHolder对SwitchButton的OnCheckedChangeListener进行监听(开关状态被改变)在更新UI。编译安装后发现实际效果不对。OnCheckedChangeListener会先OnClickListener一步监听到点击事件。那么就取消OnCheckedChangeListener的监听,在OnClickListener监听完成后通过Adapter的notifyItemChanged(pos)的方式去重新绑定一次,从而刷新UI。然而SwitchButton默认设置的关闭,而SwitchButton点击事件不需要任何处理就有一个自动切换状态的动画。那就造成显示的效果为关->开->关->开。人眼明显观察到切换动画出现两次。通过查找资料和查看源码,并没有发现有设置取消自动更新动画的方法。

这个中间使用了各种方法来解决这些冲突都没有很好解决(新手一枚),国庆的一个下午和一整个晚上,中间很多过程也记不清了,多次差点放弃。终于找到了一个可算还行的方法。

 

三、最终实现

首先在Fragement监听OnClickListener,直接看注释即可。

myAdapter.setSwitchListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        int pos = (int) view.getTag();
        //根据 当前状态判断应当发送的消息
        String msg = myAdapter.getBemfaDeviceList().get(pos).getDevice().isOpen() ? DEVICE_CLOSE : DEVICE_OPEN;
        //发送消息到巴法云,异步处理返回的消息
        sendMsgToDevice(myAdapter.getBemfaDeviceList().get(pos), msg, new OnSendStatusListener() {
            @Override
            public void onSendStatus(boolean isSuccess) {
                //根据发送的消息,判断接下来应当处于什么状态
                boolean nextStatus = isSuccess != msg.equals(DEVICE_CLOSE);
                myAdapter.getBemfaDeviceList().get(pos).getDevice().setOpenStatus(nextStatus);

                //同步锁,告诉view对象,已经收到了来自服务器的消息,可以更新UI
                synchronized((SwitchButton)view) {
                    view.setSelected(true);
                    view.notify();
                }

            }
        });
    }
});

接下来就是在容器内在onBindViewHolder监听OnCheckedChangeListener,如下注释很详细了

holder.grid_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        // buttonView.setChecked(!isChecked);
        if(switchListener == null) {
            return;
        }
        //这里就不管滑动还是点击,都当做单击处理
        switchListener.onClick(holder.grid_switch);

        ThreadPoolExecutor poolExecutor;
        //在线程池获取一个线程(当前是处于主线程的,不在子线程通过,会直接阻塞主线程造成无响应)
        poolExecutor = ExecutorUtil.getInstance().newSingleThreadExecutor();
        poolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                //同步,等待数据获取完成再更新UI,条件是SwitchButton当前被选中
                synchronized(holder.grid_switch) {
                    while(!holder.grid_switch.isSelected()) {
                        try {
                            holder.grid_switch.wait();
                            logcat("" + holder.grid_switch.isSelected() + Thread.currentThread().toString());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //数据获取完成后在主线程更新UI
                    ((Activity)con).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //重置条件,记得在最开始初始化的时候就得先重置一次
                            holder.grid_switch.setSelected(false);
                            //更新SwitchButton状态
                            holder.grid_switch.setCheckedNoEvent(mBemfaDeviceList.get(position).getDevice().isOpen());
                            //更新显示的图标
                            holder.grid_img.setImageResource(mBemfaDeviceList.get(position).getDevice().getImgId());
                        }
                    });

                }

            }
        });
    }
});

这里说下处理的流程:

  1.   不管是滑动还是点击,最先监听到OnCheckedChangeListener,现在无法判断这个来自滑动还是监听。
  2.   不管滑动还是点击,都当做单击处理,触发OnClickListener事件。
  3.   在OnClickListener中给服务器发送消息,并异步处理接收到的消息
  4.   在OnCheckedChangeListener等待OnClickListener收到服务器消息的通知
  5.   OnClickListener收到消息后通知OnCheckedChangeListener的SwitchButton对象,并更新条件。
  6.   同步锁调节不在满足,开始更新UI。

 

四、问题点

这里在说明一下问题点,点击后可以正常的在接收后,在根据实际改变按钮状态。但是如果采用滑动的方式,这个就会在消息接手前就改变状态,在正常处理没啥说的,异常就回退到之前的状态,其实也还行。毕竟就设置不成功嘛。

如果在OnCheckedChangeListener内,提前屏蔽状态buttonView.setChecked(!isChecked);比如监听到改变为true,那么之前就是false。重新设置回去即可,但是这样就会在正常也出现上面提到的异常时的情况。滑到true,强制回退,在收到消息时再自动选择状态。

其实最好的函数就是禁止滑动,不知道是没有还是咋地,反正目前没找到。

 


2021.10.10补充
可以可以在SwitchButton布局外围加一个控制头,不在监听SwitchButton,而且监听这个控制头实现点击操作并且屏蔽了滑动操作。可以在成功/失败后在更新状态


如未注明 , 均为原创。转载请注明原文链接:关于Android的SwitchButton的一些坑
喜欢 (0)