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

█ 【安卓学习之常见问题】初始化Spinner、CheckBox和SeekBar不触发事件 █ 系列文章目录 提示:这里是收集了和文件分享有关的文章

  • 【安卓学习之常见问题】android路径及文件问题
  • 【安卓学习之常见问题】文件分享–文件不存在
    • 【安卓学习之常见问题】自定义组件-刷新后跳到第一行
  • 【安卓学习之常见问题】初始化spinner、CheckBox和SeekBar不触发事件
█ 文章目录

  • █ 【安卓学习之常见问题】初始化Spinner、CheckBox和SeekBar不触发事件
  • █ 系列文章目录
  • █ 文章目录
  • █ 读前说明
  • █ 问题
  • █ Spinner不触发事件
  • █ CheckBox不触发事件
  • █ SeekBar不触发事件
  • █ 相关资料
  • █ 免责声明

█ 读前说明
  • 本文通过学习别人写demo,学习一些课件,参考一些博客,’学习相关知识,如果涉及侵权请告知
  • 本文只简单罗列相关的代码实现过程
  • 涉及到的逻辑以及说明也只是简单介绍,主要当做笔记,了解过程而已
█ 问题
  • 数据会自动刷新,每次控件都要重新赋值,赋值过程又触发监听,写数据,造成 每次读的时候也写。
█ Spinner不触发事件
  • 正常初始化
ArrayAdapter adapter = new ArrayAdapter(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);

>>>>结果发现是先执行
监听置null setOnItemSelectedListener(null)
设置值 setSelection(true)
监听置this setOnItemSelectedListener(this)
触发监听 onItemSelected()
监听白设置了
  • 使用setSelection(true,false)
mSpinner.setSelection(true, false);

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

>>>>结果不执行监听onItemSelected(),满足要求
监听置null setOnItemSelectedListener(null)
设置值 setSelection(true, false)
触发监听 onItemSelected()
监听置this setOnItemSelectedListener(this)
  • AppCompatSpinner
    #|【安卓学习之常见问题】初始化Spinner、CheckBox和SeekBar不触发事件
    文章图片
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); } }

监听置null setOnItemSelectedListener(null)
设置值 setSelection(true)
请求重新布局
重新测量布局绘制
会触发onLayout方法
requestLayout()
->View的测量 performMeasure()
->View的布局 performLayout()
->View的绘制 performDraw()
刷新onDraw invalidate()
监听置this setOnItemSelectedListener(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.Spinnerlayout(3, false) mBlockLayoutRequests = false; } }

>>>>执行layout(delta, animate) 就和 之前的一样
监听置null setOnItemSelectedListener(null)
设置值 setSelection(true, false)
->setSelectionInt()
确定View本身的位置
即设置View本身的四个顶点位置
layout(3, false)
选项更新 checkSelectionChanged()
->selectionChanged()
->无监听事跳过post(mSelectionNotifier)
监听置this setOnItemSelectedListener(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”
█ 免责声明
博主分享的所有文章内容,部分参考网上教程,引用大神高论,部分亲身实践,记下笔录,内容可能存在诸多不实之处,还望海涵,本内容仅供学习研究使用,切勿用于商业用途,若您是部分内容的作者,不喜欢此内容被分享出来,可联系博主说明相关情况通知删除,感谢您的理解与支持!
【#|【安卓学习之常见问题】初始化Spinner、CheckBox和SeekBar不触发事件】转载请注明出处:
https://blog.csdn.net/ljb568838953/article/details/126637399

    推荐阅读