自定义SwitchPreference样式

先来看看APIDemo中的Preference是什么样子.

image.png

这里个效果实现起来挺简单的,不需要自己写布局文件,只需要在res/xml目录下配置一下自己的设置选项,简单的加载一下就可以实现.但是如果你想要改变一下样式,该怎么办呢?(不了解基本用法的请自行百度)

接下来看看一下有哪些属性值是可以修改的,我们可不可以通过修改系统提供的属性达到修改样式的效果呢?

android:key="checkbox_preference"
android:title="@string/title_switch_preference"
android:summary="@string/summary_switch_preference_yes_no"
android:switchTextOn = "YES"
android:switchTextOff = "NO"
android:defaultValue="true" />

由于Preference是采用SharedPreference保存数据的,这里的属性key表示开关状态.defaultValue表示默认存储的值

我们发现并没有提供可供修改样式的属性,比如背景色,Switch的track属性,thumb属性.

接下里分析一下SwitchPreference的源代码:
继承结构如下
SwitchPreference->TwoStatePreference->Preference

首先SwitchPreference解析attrs

public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    TypedArray a = context.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.SwitchPreference, defStyleAttr, defStyleRes);
    setSummaryOn(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOn));
    setSummaryOff(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOff));
    setSwitchTextOn(a.getString(
            com.android.internal.R.styleable.SwitchPreference_switchTextOn));
    setSwitchTextOff(a.getString(
            com.android.internal.R.styleable.SwitchPreference_switchTextOff));
    setDisableDependentsState(a.getBoolean(
            com.android.internal.R.styleable.SwitchPreference_disableDependentsState, false));
    a.recycle();
}

可以看到,解析到到了诸如SummaryOff,SummaryOn等属性,并没有background的字眼.

接着看点到super(context, attrs, defStyleAttr, defStyleRes);中看看,这里直接进到了Preference中.

public Preference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    mContext = context;

    final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
    for (int i = a.getIndexCount() - 1; i >= 0; i--) {
        int attr = a.getIndex(i); 
        switch (attr) {
            case com.android.internal.R.styleable.Preference_icon:
                mIconResId = a.getResourceId(attr, 0);
                break;

            case com.android.internal.R.styleable.Preference_key:
                mKey = a.getString(attr);
                break;
                
            case com.android.internal.R.styleable.Preference_title:
                mTitleRes = a.getResourceId(attr, 0);
                mTitle = a.getString(attr);
                break;
                
            case com.android.internal.R.styleable.Preference_summary:
                mSummary = a.getString(attr);
                break;
                
            case com.android.internal.R.styleable.Preference_order:
                mOrder = a.getInt(attr, mOrder);
                break;

            case com.android.internal.R.styleable.Preference_fragment:
                mFragment = a.getString(attr);
                break;

            case com.android.internal.R.styleable.Preference_layout:
                mLayoutResId = a.getResourceId(attr, mLayoutResId);
                break;

            case com.android.internal.R.styleable.Preference_widgetLayout:
                mWidgetLayoutResId = a.getResourceId(attr, mWidgetLayoutResId);
                break;
                
            case com.android.internal.R.styleable.Preference_enabled:
                mEnabled = a.getBoolean(attr, true);
                break;
                
            case com.android.internal.R.styleable.Preference_selectable:
                mSelectable = a.getBoolean(attr, true);
                break;
                
            case com.android.internal.R.styleable.Preference_persistent:
                mPersistent = a.getBoolean(attr, mPersistent);
                break;
                
            case com.android.internal.R.styleable.Preference_dependency:
                mDependencyKey = a.getString(attr);
                break;
                
            case com.android.internal.R.styleable.Preference_defaultValue:
                mDefaultValue = onGetDefaultValue(a, attr);
                break;
                
            case com.android.internal.R.styleable.Preference_shouldDisableView:
                mShouldDisableView = a.getBoolean(attr, mShouldDisableView);
                break;
        }
    }
    a.recycle();

    if (!getClass().getName().startsWith("android.preference")
            && !getClass().getName().startsWith("com.android")) {
        // For non-framework subclasses, assume the worst and don't cache views.
        mCanRecycleLayout = false;
    }
}

可以看到,这里也没有背景色等设置.但是我们看到了几个关键的属性.mLayoutResId,mWidgetLayoutResId.从名字可以猜的出来他们是一些layout的id.那们是在哪里赋值的呢?至少我们没有手动的给他设置.

按照自定义View的一般套路,拿到属性值就该给拿这些属性值搞事情了,搜啊搜,在Preference中找到了下面这个

private int mLayoutResId = com.android.internal.R.layout.preference;

可以看到mLayoutResId 这个id对应的是系统文件中的com.android.internal.R.layout.preference

该文件在framework/base/core/res/layout目录下.
在线源码网址








    

    







可以看到这个布局文件的样子和我们看到的基本类似.其中有这样一个LinearLayout,看注释就知道这个里边要动态想里边添加控件的.看到这里你应该可以猜到上面提到的mWidgetLayoutResId的作用了吧.



接下来看SwitchPreference的mWidgetLayoutResId是在哪里赋值的.找这个这个就比较麻烦了.首先你要明白在我们自定义View的时候构造函数中这个几个参数的含义.关键是defStyleAttr的含义,建议参考该地址

public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}
 public SwitchPreference(Context context, AttributeSet attrs) {
    this(context, attrs, com.android.internal.R.attr.switchPreferenceStyle);
}

可以看到这里指定了com.android.internal.R.attr.switchPreferenceStyle这样一个属性.我们到系统样式/frameworks/base/core/res/res/values/themes.xml
中找到

 @android:style/Preference.SwitchPreference

下面看看@android:style/Preference.SwitchPreference中的内容


这个文件指定了mWidgetLayoutResId所指向的布局文件.

到这里,我们基本上明白了SwitchPreference的UI是如何加载出来的.我们只要把mWidgetLayoutResId,mLayoutResId换乘我们自己的布局文件就可以实现样式的修改.

当然,当我们吧布局文件更换之后,View的id也变了.因此SwitchPreference的onCreateView,onBindView方法需要重写.只需要将系统的id换乘我们自己的id就可以了.

系统源码如下:

@Override
protected void onBindView(View view) {
    super.onBindView(view);

    View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
    if (checkableView != null && checkableView instanceof Checkable) {
        if (checkableView instanceof Switch) {
            final Switch switchView = (Switch) checkableView;
            switchView.setOnCheckedChangeListener(null);
        }

        ((Checkable) checkableView).setChecked(mChecked);

        if (checkableView instanceof Switch) {
            final Switch switchView = (Switch) checkableView;
            switchView.setTextOn(mSwitchOn);
            switchView.setTextOff(mSwitchOff);
            switchView.setOnCheckedChangeListener(mListener);
        }
    }

    syncSummaryView(view);
}


  @CallSuper
protected View onCreateView(ViewGroup parent) {
    final LayoutInflater layoutInflater =
        (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    
    final View layout = layoutInflater.inflate(mLayoutResId, parent, false); 
    
    final ViewGroup widgetFrame = (ViewGroup) layout
            .findViewById(com.android.internal.R.id.widget_frame);
    if (widgetFrame != null) {
        if (mWidgetLayoutResId != 0) {
            layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
        } else {
            widgetFrame.setVisibility(View.GONE);
        }
    }
    return layout;
}

下面给出我的实现,基本上就是照抄SwitchPreference :

public class MySwitchPreference extends SwitchPreference {
private final Listener mListener = new Listener();
private Drawable mIcon;
public MySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {

    super(context, attrs, defStyleAttr);
    setLayoutResource(R.layout.preference);//设置自己的更布局
}

  public MySwitchPreference(Context context, AttributeSet attrs) {

    this(context, attrs, R.attr.mySwitchPreference);//替换成我们自己的样式,同样需要配置自己的主题
}

public MySwitchPreference(Context context) {

    this(context, null);
}

@Override
protected View onCreateView(ViewGroup parent) {
    super.onCreateView(parent);//自己处理View的创建
    final LayoutInflater layoutInflater =
            (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    final View layout = layoutInflater.inflate(getLayoutResource(), parent, false);

    final ViewGroup widgetFrame = (ViewGroup) layout
            .findViewById(R.id.widget_frame);
    if (widgetFrame != null) {
        if (getWidgetLayoutResource() != 0) {
            layoutInflater.inflate(getWidgetLayoutResource(), widgetFrame);
        } else {
            widgetFrame.setVisibility(View.GONE);
        }
    }

    return layout;
}

@Override
protected void onBindView(View view) {

    super.onBindView(view);//自己处理View 的绑定

    final TextView titleView = (TextView) view.findViewById(R.id.title);
    if (titleView != null) {
        final CharSequence title = getTitle();
        if (!TextUtils.isEmpty(title)) {
            titleView.setText(title);
            titleView.setVisibility(View.VISIBLE);
        } else {
            titleView.setVisibility(View.GONE);
        }
    }


    final ImageView imageView = (ImageView) view.findViewById(R.id.icon);
    try {//反射资源id
        Class clazz = getClass();
        Field Field = clazz.getDeclaredField("mIconResId");
        Field.setAccessible(true);
        int mIconResId = (int) Field.get(this);
        if (imageView != null) {
            if (mIconResId != 0 || mIcon != null) {
                if (mIcon == null) {
                    mIcon = getContext().getResources().getDrawable(mIconResId);
                }
                if (mIcon != null) {
                    imageView.setImageDrawable(mIcon);
                }
            }
            imageView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
        }

    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    View checkableView = view.findViewById(R.id.switchWidget);
    if (checkableView != null && checkableView instanceof Checkable) {
        if (checkableView instanceof Switch) {
            final Switch switchView = (Switch) checkableView;
            switchView.setOnCheckedChangeListener(null);
        }

        ((Checkable) checkableView).setChecked(isChecked());

        if (checkableView instanceof Switch) {
            final Switch switchView = (Switch) checkableView;
            switchView.setTextOn(getSwitchTextOn());
            switchView.setTextOff(getSwitchTextOff());
            switchView.setOnCheckedChangeListener(mListener);
        }
    }



}

private class Listener implements CompoundButton.OnCheckedChangeListener {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (!callChangeListener(isChecked)) {
            // Listener didn't like it, change it back.
            // CompoundButton will make sure we don't recurse.
            buttonView.setChecked(!isChecked);
            return;
        }

        MySwitchPreference.this.setChecked(isChecked);
    }
}

}

你可能感兴趣的:(自定义SwitchPreference样式)