Android(12)浅析 偏好设置 Preference(一)

Android(12)浅析 偏好设置 Preference(一),第1张

Android(12)浅析 偏好设置 Preference(一) Android(12)浅析 偏好设置 Preference(一)

### 官方基本用法:https://developer.android.google.cn/guide/topics/ui/settings

效果演示:


源码解读路线:

Preference需要配合PreferenceFragmentCompat使用。

所以和平时在Activity中使用Fragment没有区别,只是布局文件不是从layout文件夹去渲染而是从xml文件夹。

列表的展示是用了RecyclerView实现的,Apdater是PreferenceGroupAdpater,VH是PreferenceViewHolder

Preference要关心的地方,基本布局文件是什么?,扩展布局文件是什么,怎么设置点击事件的呢?,怎么监听值的变化的呢?,我该如何自定义呢?等…

准备动手!准备动手!

先来看一下Preference默认的布局:

使用AS定位到文件:R.layout.preference






    
        
    

    

        

        

    

    
    



先从UI动手:由于是RecyclerView实现的,还是很清晰的,先进入PreferenceGroupAdapter

先看数据源、三大重写大方法onCreateViewHolder()、onBindViewHolder()、getItemCount()

public class PreferenceGroupAdapter extends RecyclerView.Adapter
        implements Preference.OnPreferenceChangeInternalListener,
        PreferenceGroup.PreferencePositionCallback {
            
            // ...
            
            // 数据集
            private List mPreferences;
            // 可见的Preference
            private List mVisiblePreferences;
            
            @Override
    		public int getItemCount() {
        		return mVisiblePreferences.size();
    		}
            
    // onCreateViewHolder()
            
    @Override
    @NonNull
    public PreferenceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 先获取Preference的资源描述类,这个类内部存储了一个Preference的Class名,基本布局资源id,扩展布		   // 局资源id
        final PreferenceResourceDescriptor descriptor = mPreferenceResourceDescriptors.get(
                viewType);
        
        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        
        // 设置被选中时的水波纹
        TypedArray a = parent.getContext().obtainStyledAttributes(null, R.styleable.BackgroundStyle);
        Drawable background
                = a.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground);
        if (background == null) {
            background = AppCompatResources.getDrawable(parent.getContext(),
                    android.R.drawable.list_selector_background);
        }
        a.recycle();
        
		// 构造VH,看,这里inflate的是decriptor.mLoutRestId,上面说了,这个值保存的是Prefercen的及颁布布局,也就是对应着xml中的layout属性
        final View view = inflater.inflate(descriptor.mLayoutResId, parent, false);
        if (view.getBackground() == null) {
            ViewCompat.setBackground(view, background);
        }
		
        // 这里判断如果设置了widgetLayout属性,这里会同时渲染
        final ViewGroup widgetframe = view.findViewById(android.R.id.widget_frame);
        if (widgetframe != null) {
            if (descriptor.mWidgetLayoutResId != 0) {
                inflater.inflate(descriptor.mWidgetLayoutResId, widgetframe);
            } else {
                widgetframe.setVisibility(View.GONE);
            }
        }

        return new PreferenceViewHolder(view);
    }
            
    // 绑定数据-----------------> Preference.onBindViewHolder()     
    @Override
    public void onBindViewHolder(@NonNull PreferenceViewHolder holder, int position) {
        final Preference preference = getItem(position);
        preference.onBindViewHolder(holder);
    }
            
            // ...
            
}

走到这里,我们就明白了为什么自定义Preference需要重写的方法为什么是onBindViewHolder()了,在适配器的onBindViewHHolder()中是直接委托给了Preference实现。

接着走,去Preference#onBindViewHolder()瞅一瞅:

Preference#onBindViewHolder(): 翻译的好啊,好地方!

public void onBindViewHolder(PreferenceViewHolder holder) {
        View itemView = holder.itemView;
        Integer summaryTextColor = null;

    	// 设置点击事件,划重点 mClickListener
        itemView.setOnClickListener(mClickListener);
        itemView.setId(mViewId);
		
    	// 配置 概述
        final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
        if (summaryView != null) {
            final CharSequence summary = getSummary();
            if (!TextUtils.isEmpty(summary)) {
                summaryView.setText(summary);
                summaryView.setVisibility(View.VISIBLE);
                summaryTextColor = summaryView.getCurrentTextColor();
            } else {
                summaryView.setVisibility(View.GONE);
            }
        }
		
    	// 配置 标题
        final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
        if (titleView != null) {
            final CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                titleView.setText(title);
                titleView.setVisibility(View.VISIBLE);
                if (mHasSingleLineTitleAttr) {
                    titleView.setSingleLine(mSingleLineTitle);
                }
                // If this Preference is not selectable, but still enabled, we should set the
                // title text colour to the same colour used for the summary text
                if (!isSelectable() && isEnabled() && summaryTextColor != null) {
                    titleView.setTextColor(summaryTextColor);
                }
            } else {
                titleView.setVisibility(View.GONE);
            }
        }

        // 配置 小图标
        final ImageView imageView = (ImageView) holder.findViewById(android.R.id.icon);
        if (imageView != null) {
            if (mIconResId != 0 || mIcon != null) {
                if (mIcon == null) {
                    mIcon = AppCompatResources.getDrawable(mContext, mIconResId);
                }
                if (mIcon != null) {
                    imageView.setImageDrawable(mIcon);
                }
            }
            if (mIcon != null) {
                imageView.setVisibility(View.VISIBLE);
            } else {
                imageView.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
            }
        }

    	
        View imageframe = holder.findViewById(R.id.icon_frame);
        if (imageframe == null) {
            imageframe = holder.findViewById(AndroidResources.ANDROID_R_ICON_frame);
        }
        if (imageframe != null) {
            if (mIcon != null) {
                imageframe.setVisibility(View.VISIBLE);
            } else {
                imageframe.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
            }
        }

        if (mShouldDisableView) {
            setEnabledStateOnViews(itemView, isEnabled());
        } else {
            setEnabledStateOnViews(itemView, true);
        }

        final boolean selectable = isSelectable();
        itemView.setFocusable(selectable);
        itemView.setClickable(selectable);

    	// 设置上下分割线
        holder.setDividerAllowedAbove(mAllowDividerAbove);
        holder.setDividerAllowedBelow(mAllowDividerBelow);

        final boolean copyingEnabled = isCopyingEnabled();

        if (copyingEnabled && monCopyListener == null) {
            monCopyListener = new OnPreferenceCopyListener(this);
        }
        itemView.setOnCreateContextMenuListener(copyingEnabled ? monCopyListener : null);
        itemView.setLongClickable(copyingEnabled);

        // Remove touch ripple if the view isn't selectable
        if (copyingEnabled && !selectable) {
            ViewCompat.setBackground(itemView, null);
        }
    }

OKK,我们知道了点击事件是直接绑定在itemView上的,我们来看看这个mClickListener:

private final View.onClickListener mClickListener = new View.onClickListener() {
        @Override
        public void onClick(View v) {
        	// 处理点击事件,执行performClick()方法
            performClick(v);
        }
    };

继续走------------> Preference#performClick(View)

@RestrictTo(LIBRARY_GROUP_PREFIX)
protected void performClick(View view) {
   // 继续分发套娃
   performClick();
}

好家伙,经典套娃,这次一定是具体实现 -----> Preference#performClick()

@RestrictTo(LIBRARY_GROUP_PREFIX)
    public void performClick() {
		
        // 不可用或者不可点击的话直接return
        if (!isEnabled() || !isSelectable()) {
            return;
        }
        
		// 这个onClick()是一个空方法,可以在Preference设置了点击事件之前可以处理一些自己的逻辑
        onClick();

        // 朴实无华的点击分发,如果给Preference设置了点击事件(Preference$setOnPreferenceClickListerner)则调用接口方法处理自己的逻辑
        if (monClickListener != null && mOnClickListener.onPreferenceClick(this)) {
            return;
        }
		
        
        // 假如没有设置点击事件的话,这个点击事件没有完全消费掉(就是onPreferenceClick(this)为false)
        // 这里会执行另一个接口方法 PreferenceManager.OnPreferenceTreeClickListener
        // 这个方法的实现在 PreferenceFragmentCompat中,也就是我们可以通过这个方法得到当前点击的是哪一个
        // Preference
        PreferenceManager preferenceManager = getPreferenceManager();
        if (preferenceManager != null) {
            PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
                    .getOnPreferenceTreeClickListener();
            if (listener != null && listener.onPreferenceTreeClick(this)) {
                return;
            }
        }
        
		// 这里就是上面的点击事件,全局点击事件都没有完全消费的话,这里就是直接执行节点
        // 就是启动一个 Activity
        if (mIntent != null) {
            Context context = getContext();
            context.start到此Activity(mIntent);
        }
    }

到此,onBindView()就搞定了,UI也就在Fragment中显示出来了

怎么自定义Preference嘞?

通过上面的分析啊,我们知道俩个属性分别在哪里被用到了,layout、widgetLyaout,那么我们自定义布局的时候如果还是采用原生那个排列的话,就是上面那个效果的话,就是直接把默认布局抄一遍,然后就可以改样式了,如果是完全非主流的样式就是自己搞个布局然后赋值给layout或者在代码里赋值给layoutResource布局搞好之后,就得需要对布局中的控件做一些配置,这个时候就得用到Preference#onBindViewHolder()这个方法了,用法和ReyclerView.Adapter.onBindViewHolder()一样同时,Preference也封装了notifyDataSetChanged()方法:Preference#notifyChanged()

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/5715522.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-17
下一篇2022-12-17

发表评论

登录后才能评论

评论列表(0条)

    保存