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

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

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

效果演示:

在这里插入图片描述
在这里插入图片描述

源码解读路线:

Preference需要配合PreferenceFragmentCompat使用。

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

  • 列表的展示是用了RecyclerView实现的,ApdaterPreferenceGroupAdpaterVHPreferenceViewHolder

  • 在这里插入图片描述

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

准备动手!准备动手!

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

在这里插入图片描述

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

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2015 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical"
    android:paddingEnd="?android:attr/scrollbarSize"
    android:paddingRight="?android:attr/scrollbarSize"
    android:background="?android:attr/selectableItemBackground">

    <FrameLayout
        android:id="@+id/icon_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <androidx.preference.internal.PreferenceImageView
            android:id="@android:id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:maxWidth="48dp"
            app:maxHeight="48dp" />
    </FrameLayout>

    <RelativeLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dip"
        android:layout_marginLeft="15dip"
        android:layout_marginEnd="6dip"
        android:layout_marginRight="6dip"
        android:layout_marginTop="6dip"
        android:layout_marginBottom="6dip"
        android:layout_weight="1">

        <TextView android:id="@android:id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="?android:attr/textColorPrimary"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal" />

        <TextView android:id="@android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@android:id/title"
            android:layout_alignStart="@android:id/title"
            android:layout_alignLeft="@android:id/title"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="?android:attr/textColorSecondary"
            android:maxLines="4" />

    </RelativeLayout>

    <!-- Preference should place its actual preference widget here. -->
    <LinearLayout android:id="@android:id/widget_frame"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:orientation="vertical" />

</LinearLayout>

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

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

    public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewHolder>
            implements Preference.OnPreferenceChangeInternalListener,
            PreferenceGroup.PreferencePositionCallback {
                
                // ...
                
                // 数据集
                private List<Preference> mPreferences;
                // 可见的Preference
                private List<Preference> 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;
            }
        }
        
		// 这里就是上面的点击事件,全局点击事件都没有完全消费的话,这里就是直接执行<intent/>节点
        // 就是启动一个 Activity
        if (mIntent != null) {
            Context context = getContext();
            context.start到此Activity(mIntent);
        }
    }

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

怎么自定义Preference嘞?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值