【安卓学习之常见问题】初始化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);
    @Override
    public void onItemSelected(AdapterView<?> adapterView, View view, int position,
                               long id) {
        。。。。。。
    }
  • 刷新数据:习惯是先将监听置null,设置后,再重复赋值
      mSpinner.setOnItemSelectedListener(null);
      mSpinner.setSelection(true);// 监听异步执行,延迟执行
      mSpinner.setOnItemSelectedListener(this);

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

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

监听白设置了

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

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

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

>>>>结果不执行监听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方法
        }
    }
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);
    }

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);
        }
    }
监听置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;
        }
    }

>>>>执行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();
		。。。。。。
    }

android.widget.AbsSpinner

@Override
    public void setAdapter(SpinnerAdapter adapter) {
		。。。。。。
        requestLayout();
    }

█ CheckBox不触发事件

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

>>>>结果OK

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

█ SeekBar不触发事件

  • 正常初始化
    mSeekBar.setOnSeekBarChangeListener(this);
    // 拖动条进度改变的时候调用(不触发监听,修改显示值)
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (fromUser) {
        	。。。。。。
        }
    }

    // 拖动条开始拖动的时候调用
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        。。。。。。
    }

    // 拖动条停止拖动的时候调用(触发监听,修改参数)
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        。。。。。。
    }
  • 刷新数据:因为监听里面自带fromUser参数判断是否是手动拖动,因此可以直接修改
     mSeekBar.setProgress(progress);

>>>>结果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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值