• 【安卓学习之常见问题】初始化Spinner、CheckBox和SeekBar不触发事件


    █ 【安卓学习之常见问题】初始化Spinner、CheckBox和SeekBar不触发事件


    █ 系列文章目录

    提示:这里是收集了和文件分享有关的文章

    █ 文章目录


    █ 读前说明

    • 本文通过学习别人写demo,学习一些课件,参考一些博客,’学习相关知识,如果涉及侵权请告知
    • 本文只简单罗列相关的代码实现过程
    • 涉及到的逻辑以及说明也只是简单介绍,主要当做笔记,了解过程而已    

    █ 问题

    • 数据会自动刷新,每次控件都要重新赋值,赋值过程又触发监听,写数据,造成 每次读的时候也写。

    █ Spinner不触发事件

    • 正常初始化
    		ArrayAdapter<String> adapter = new ArrayAdapter<String>(viewGroup.getContext(),
    				android.R.layout.simple_spinner_item, datas);
    		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    		mSpinner.setAdapter(adapter);
    		mSpinner.setSelection(mPosition, true);
    		mSpinner.setOnItemSelectedListener(this);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int position,
                                   long id) {
            。。。。。。
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 刷新数据:习惯是先将监听置null,设置后,再重复赋值
          mSpinner.setOnItemSelectedListener(null);
          mSpinner.setSelection(true);// 监听异步执行,延迟执行
          mSpinner.setOnItemSelectedListener(this);
    
    • 1
    • 2
    • 3

    >>>>结果发现是先执行

    监听置nullsetOnItemSelectedListener(null)
    设置值setSelection(true)
    监听置thissetOnItemSelectedListener(this)
    触发监听onItemSelected()

    监听白设置了

    • 使用setSelection(true,false)
          mSpinner.setSelection(true, false);
    
    • 1

    >>>>结果还是是执行监听onItemSelected()

    • 将上面两种方法合起来执行,测试效果
          mSpinner.setOnItemSelectedListener(null);
          mSpinner.setSelection(true, false);// 监听同步执行,没延迟执行
          mSpinner.setOnItemSelectedListener(this);
    
    • 1
    • 2
    • 3

    >>>>结果不执行监听onItemSelected(),满足要求

    监听置nullsetOnItemSelectedListener(null)
    设置值setSelection(true, false)
    触发监听onItemSelected()
    监听置thissetOnItemSelectedListener(this)
    • AppCompatSpinner
      在这里插入图片描述
    setSelection(int position)Sets the currently selected item.设置当前选中的项目。
    setSelection(int position, boolean animate)Jump directly to a specific item in the adapter data.直接跳转到适配器数据中的特定项目。
    • 研究下源码setSelection(int position)
      android.widget.AbsSpinner
        @Override
        public void setSelection(int position) {// 3
            setNextSelectedPositionInt(position);// android.widget.AdapterView
            requestLayout();
            invalidate();// 刷新
        }
        /**
    	  * 保持 mNextSelectedPosition 和 mNextSelectedRowId 同步的实用程序
          * @param position 下次我们去时 mSelectedPosition 的预期值
          * 通过布局
         */
        void setNextSelectedPositionInt(int position) {
            mNextSelectedPosition = position;// 决定当前选中的是哪一个位置的值:3
            mNextSelectedRowId = getItemIdAtPosition(position);
            // 如果我们试图同步到选择,也更新它
            if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {// mNeedSync = false
                。。。
            }
        }
        /**
         * 覆盖以防止向自己发送布局请求垃圾邮件
         * 当我们放置视图时
         * @see android.view.View#requestLayout()
         */
        @Override
        public void requestLayout() {
            if (!mBlockLayoutRequests) {// mBlockLayoutRequests = false,因此执行
                super.requestLayout();// 请求重新布局,此时会触发onLayout方法
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    requestLayout()要求parent view重新进行一次测量、布局、绘制这三个流程来更新自己位置。重新布局自己在父布局中的位置
    invalidate()view进行重新绘制强制调用自己的onDraw()方法

    会异步执行layout(int delta, boolean animate):

    android.widget.Spinner

        /**
         * @see android.view.View#onLayout(boolean,int,int,int,int)
         * Creates and positions all views
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            mInLayout = true;
            layout(0, false);
            mInLayout = false;
        }
        /**
          * 创建并定位此 Spinner 的所有视图。
          * @param delta 更改所选位置。 +1 表示选择向右移动,
          * 所以视图向左滚动。 -1 表示选择向左移动。
         */
        @Override
        void layout(int delta, boolean animate) {
            。。。
            if (mNextSelectedPosition >= 0) {// 要设置的位置
                setSelectedPositionInt(mNextSelectedPosition);// mSelectedPosition = mNextSelectedPosition = 3
            }
            recycleAllViews();
            removeAllViewsInLayout();
            // Make selected view and position it
            mFirstPosition = mSelectedPosition;
            if (mAdapter != null) {
                View sel = makeView(mSelectedPosition, true);// 生成AppCompatTextView{mText ="3"}
                。。。。。。
            }
            // Flush any cached views that did not get reused above
            mRecycler.clear();
            invalidate();//刷新
            checkSelectionChanged();// android.widget.AdapterView
            mDataChanged = false;
            mNeedSync = false;
            setNextSelectedPositionInt(mSelectedPosition);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    android.widget.AdapterView

        /**
          * 布局后调用判断选择位置是否需要被更新。 也用于触发任何未决的选择事件。
         */
        void checkSelectionChanged() {
        	// 3 != -1,执行selectionChanged();
            if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
                selectionChanged();
                mOldSelectedPosition = mSelectedPosition;
                mOldSelectedRowId = mSelectedRowId;
            }
    
            // If we have a pending selection notification -- and we won't if we
            // just fired one in selectionChanged() -- run it now.
            if (mPendingSelectionNotifier != null) {// null
                mPendingSelectionNotifier.run();
            }
        }
        
    	void selectionChanged() {
    		// 即将发布或运行选择通知程序,所以我们不需要一个挂起的通知器。
            mPendingSelectionNotifier = null;
            // 辅助功能服务AccessibilityService(无障碍服务)
    	    // 如果spinner的监听为null则不触发,此时afm 不为null
    	    // 如果spinner的监听不为null则触发,此时afm 不为null
            if (mOnItemSelectedListener != null
                    || AccessibilityManager.getInstance(mContext).isEnabled()) {
                if (mInLayout || mBlockLayoutRequests) {
                    。。。。。。
                    if (mSelectionNotifier == null) {
                        mSelectionNotifier = new SelectionNotifier();
                    } else {
                        removeCallbacks(mSelectionNotifier);
                    }
                    post(mSelectionNotifier);// 有监听,执行这句
                } else {
                    dispatchOnItemSelected();
                }
            }
    		。。。。。。
        }
        
        private class SelectionNotifier implements Runnable {
            public void run() {
                。。。。。。
                if (mDataChanged && getViewRootImpl() != null
                        && getViewRootImpl().isLayoutRequested()) {
                    。。。。。。
                } else {
                    dispatchOnItemSelected();// 执行这句
                }
            }
        }
        
        private void dispatchOnItemSelected() {
            fireOnSelected();// 执行这句
            performAccessibilityActionsOnSelected();
        }
        
        private void fireOnSelected() {
            if (mOnItemSelectedListener == null) {// 对接外部的Spinner监听
                return;
            }
            final int selection = getSelectedItemPosition();
            if (selection >= 0) {
                View v = getSelectedView();
                mOnItemSelectedListener.onItemSelected(this, v, selection,
                        getAdapter().getItemId(selection));// 执行这句
            } else {
                mOnItemSelectedListener.onNothingSelected(this);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    监听置nullsetOnItemSelectedListener(null)
    设置值setSelection(true)
    请求重新布局
    重新测量布局绘制
    会触发onLayout方法
    requestLayout()
    ->View的测量 performMeasure()
    ->View的布局 performLayout()
    ->View的绘制 performDraw()
    刷新onDrawinvalidate()
    监听置thissetOnItemSelectedListener(this)
    延迟延迟
    设置自身和子布局的位置
    调用子元素的layout方法
    完成View树的layout过程
    onLayout(boolean changed, int l, int t, int r, int b)
    确定View本身的位置
    即设置View本身的四个顶点位置
    layout(0, false)
    选项更新checkSelectionChanged()
    ->selectionChanged()
    ->有监听事件则post(mSelectionNotifier
    ->dispatchOnItemSelected()
    ->fireOnSelected()
    ->mOnItemSelectedListener.onItemSelected()
    触发外部监听onItemSelected()
    • 研究下源码setSelection(int position, boolean animate)

    android.widget.AbsSpinner

        /**
         * 直接跳转到适配器数据中的特定项目
         */
        public void setSelection(int position, boolean animate) {
            // 仅当请求的位置已经在屏幕上某处时才进行动画处理
            boolean shouldAnimate = animate && mFirstPosition <= position &&
                    position <= mFirstPosition + getChildCount() - 1;
            setSelectionInt(position, shouldAnimate);
        }
        /**
         * 选中提供位置的项目
         * @param position 要选择的位置
         * @param animate 过渡是否动画
         */
        void setSelectionInt(int position, boolean animate) {
            if (position != mOldSelectedPosition) {// 3 != -1
                mBlockLayoutRequests = true;
                int delta  = position - mSelectedPosition;// 要设置的位置-上次选中的位置=3-0=3
                setNextSelectedPositionInt(position);// android.widget.AdapterView
                layout(delta, animate);// 执行这句 android.widget.Spinner   layout(3, false)
                mBlockLayoutRequests = false;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    >>>>执行layout(delta, animate) 就和 之前的一样

    监听置nullsetOnItemSelectedListener(null)
    设置值setSelection(true, false)
    ->setSelectionInt()
    确定View本身的位置
    即设置View本身的四个顶点位置
    layout(3, false)
    选项更新checkSelectionChanged()
    ->selectionChanged()
    ->无监听事跳过post(mSelectionNotifier)
    监听置thissetOnItemSelectedListener(this)
    • 此外 mSpinner.setAdapter(adapter) 也会触发requestLayout()->layout(delta, animate)
      android.widget.Spinner
       @Override
        public void setAdapter(SpinnerAdapter adapter) {
    		。。。。。。
            super.setAdapter(adapter);
            mRecycler.clear();
    		。。。。。。
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    android.widget.AbsSpinner

    @Override
        public void setAdapter(SpinnerAdapter adapter) {
    		。。。。。。
            requestLayout();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    █ CheckBox不触发事件

    • 正常初始化
        mCheckBox.setOnCheckedChangeListener(this);
    
    • 1
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            。。。。。。
        }
    
    • 1
    • 2
    • 3
    • 4
    • 刷新数据:习惯是先将监听置null,设置后,再重复赋值
         mCheckBox.setOnCheckedChangeListener(null);
         mCheckBox.setChecked(true);// 不会触发监听
         mCheckBox.setOnCheckedChangeListener(this);
    
    • 1
    • 2
    • 3

    >>>>结果OK

    • 也可以参考SeekBar不触发事件,修改监听事件
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (buttonView.isPressed()) {
            	。。。。。。
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    SeekBar不触发事件

    • 正常初始化
        mSeekBar.setOnSeekBarChangeListener(this);
    
    • 1
        // 拖动条进度改变的时候调用(不触发监听,修改显示值)
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (fromUser) {
            	。。。。。。
            }
        }
    
        // 拖动条开始拖动的时候调用
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            。。。。。。
        }
    
        // 拖动条停止拖动的时候调用(触发监听,修改参数)
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            。。。。。。
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 刷新数据:因为监听里面自带fromUser参数判断是否是手动拖动,因此可以直接修改
         mSeekBar.setProgress(progress);
    
    • 1

    >>>>结果OK


    █ 相关资料

    提示:这里是参考的相关文章

    1. 2022-01-05 android spinner setSelection 不触发onItemSelected_sinat_42439340的博客-CSDN博客 >>>>替换成setSelection(index,false)
    2. 2013-03-20 更改复选框值而不触发onCheckChanged - 问答 - 腾讯云开发者社区-腾讯云 >>>>监听中判断checkbox.isPressed()或者 监听设置null
    3. 2021-04-07 自定义Spinner实现在set数据的时候不触发OnItemSelectedListener - 简书 >>>>继承Spinne+反射
    4. AppCompatSpinner >>>>官网
    5. 2017-12-26 浅析View的requestLayout()方法 - 简书 >>>>一句话,requestLayout()的效果是重新布局自己在父布局中的位置,invalidate()的效果是强制调用自己的onDraw()方法
    6. 2021-09-17 Android进阶之深入理解View的布局(Layout)流程原理-51CTO.COM >>>>View三大工作流程是从ViewRootImpl#performTraversals开始的,其中performMeasure、performLayout、performDraw方法分别对应了View的测量、布局、绘制;
    7. 2018-05-09 关于viewpager和fragment里面布局跳动的BUG。记录_迷人的脚毛的博客-CSDN博客 >>>>scrollview下的第一个子控件里加上 android:focusable=“true” android:focusableInTouchMode=“true”

    █ 免责声明

    博主分享的所有文章内容,部分参考网上教程,引用大神高论,部分亲身实践,记下笔录,内容可能存在诸多不实之处,还望海涵,本内容仅供学习研究使用,切勿用于商业用途,若您是部分内容的作者,不喜欢此内容被分享出来,可联系博主说明相关情况通知删除,感谢您的理解与支持!

    转载请注明出处:
    https://blog.csdn.net/ljb568838953/article/details/126637399

  • 相关阅读:
    sql 注入(1), union 联合注入
    一起Talk Android吧(第四百一十四回:使用三角函数绘制正弦波的优化)
    像FBIF一样做会展数字化,你也有可能吸引数万观众
    【前端】npm常用命令
    【CSS】笔记4-浮动
    JS-Vue-指令 JSON Ajax
    数据治理入门
    1688搜索店铺列表 API
    力扣hot100——第6天:32最长有效括号、33搜索旋转排序数组、34在排序数组中查找元素的第一个和最后一个位置
    Linux 文件和目录列表(ls 命令)
  • 原文地址:https://blog.csdn.net/ljb568838953/article/details/126637399