带你过一遍Android 多主题框架——MagicaSakura

MagicaSakura 是 Android 多主题框架。
具有以下优点:

  • 列表内容
  • 列表内容
  • 支持白天彩色主题和夜间主题。
  • 切换主题不需要重建activity
  • 提供TintXXX控件适配不同的主题,更方便更快捷。
  • 只需写一个drawable.xml 或者 layout.xml 就能自动适配到不同主题样式。
  • 兼容4.0.3以上
  • 易于集成到你的app。

Github官网
首先进入Main的布局

<FrameLayout 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="match_parent"
    android:fitsSystemWindows="true">

    <com.bilibili.magicasakura.widgets.TintToolbar
        android:id="@+id/toolbar"
        style="@style/Widget.App.Toolbar"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="@color/theme_color_primary"
        app:elevation="4dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:paddingLeft="@dimen/padding"
            android:text="@string/app_name"
            android:textColor="@color/white"
            android:textSize="22sp" />

    </com.bilibili.magicasakura.widgets.TintToolbar>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="56dp" />

    <!--底部进行提示-->
    <include layout="@layout/fragment_layout_snack" />
</FrameLayout>

对应Main怎么进入入口

//设置title
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(null);

//设置中间竖直可滑动部分
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setSmoothScrollbarEnabled(true);


//进行分割线的操作
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //8dp
        final int padding = getResources().getDimensionPixelOffset(R.dimen.padding_half);
        //返回适配器条目的位置
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
        final int position = layoutParams.getViewLayoutPosition();
        if (position == 0) {
            outRect.left = outRect.top = outRect.right = padding;
            outRect.bottom = padding >> 1;
        } else if (position == state.getItemCount() - 1) {
            outRect.left = outRect.bottom = outRect.right = padding;
            outRect.top = padding >> 1;
        } else {
            outRect.left = outRect.right = padding;
            outRect.top = outRect.bottom = padding >> 1;
        }
    }
});

//设置进行水平还是垂直
recyclerView.setLayoutManager(layoutManager);

//设置适配器
Adapter adapter = new Adapter();
recyclerView.setAdapter(adapter);

//适配器添加HolderType
adapter.addViewHolderType(
        ViewHolder.VIEW_HOLDER_HEADER,
        ViewHolder.VIEW_HOLDER_LABEL,
        ViewHolder.VIEW_HOLDER_HEADER,
        ViewHolder.VIEW_HOLDER_LOGIN,
        ViewHolder.VIEW_HOLDER_HEADER,
        ViewHolder.VIEW_HOLDER_DOWNLOAD
);
这里明白一个知识点
  1. 关于Rect outRect这个参数
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            }
}

在这个参数中Rect outRect这个矩形控制条目的显示
- outRect.left : 控制条目距离左边的距离
- outRect.right:条目距离右边的距离
- outRect.top : 条目距离上边的距离
- outRect.bottom : 条目距离下边的距离

  1. 关于padding >> 1

相当于 padding/2的值

接下来我们就看看适配器
public static class Adapter extends RecyclerView.Adapter<ViewHolder> {
    List<Integer> viewHolderTypes = new ArrayList<>();//视图类型
    SparseArrayCompat<Integer> titleIndexs = new SparseArrayCompat<>();//标题集合

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return ViewHolder.create(parent, viewType);根据类型创建每一个条目
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        //将数据绑定到item视图上
        if (holder instanceof ViewHolderHeader) {
            ((ViewHolderHeader) holder).setTitle(titleIndexs.get(position));
        }
    }

    //条目集合数
    @Override
    public int getItemCount() {
        return viewHolderTypes.size();
    }

    //根据位置得到type信息
    @Override
    public int getItemViewType(int position) {
        return viewHolderTypes.get(position);
    }

    //
    public void addViewHolderType(int... type) {
        for (int i = 0; i < type.length; i++) {
            if (type[i] == ViewHolder.VIEW_HOLDER_HEADER) {
                titleIndexs.put(i, titleIndexs.size() + 1);
            }
            viewHolderTypes.add(type[i]);
        }
        notifyDataSetChanged();
    }
}

对于适配器,最次也要实现以下几个方法

//onCreateViewHolder()负责为Item创建视图
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return null;
}
//onBindViewHolder()负责将数据绑定到Item的视图上
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
//条目个数
@Override
public int getItemCount() {
    return 0;
}

根据type创建对应的ViewHolder

public static abstract class ViewHolder extends RecyclerView.ViewHolder {
    public static final int VIEW_HOLDER_HEADER = 0;
    public static final int VIEW_HOLDER_LABEL = VIEW_HOLDER_HEADER + 1;
    public static final int VIEW_HOLDER_LOGIN = VIEW_HOLDER_LABEL + 1;
    public static final int VIEW_HOLDER_DOWNLOAD = VIEW_HOLDER_LOGIN + 1;

    public ViewHolder(View itemView) {
        super(itemView);
    }

    public static ViewHolder create(ViewGroup viewHolder, int type) {
        switch (type) {
            case VIEW_HOLDER_HEADER:
                return ViewHolderHeader.create(viewHolder);//[1.1]
            case VIEW_HOLDER_LABEL:
                return ViewHolderLabel.create(viewHolder);
            case VIEW_HOLDER_LOGIN:
                return ViewHolderLogin.create(viewHolder);
            case VIEW_HOLDER_DOWNLOAD:
                return ViewHolderChoice.create(viewHolder);
            default:
                return null;
        }
    }
}

1.1 通过上面的调用开始初始化布局

public static class ViewHolderHeader extends ViewHolder {
    private static final String[] sTitles = new String[]{"Label", "Login", "Choice"};
    TintImageView icon;
    TextView title;

    public ViewHolderHeader(View itemView) {
        super(itemView);
        icon = (TintImageView) itemView.findViewById(R.id.icon);
        title = (TextView) itemView.findViewById(R.id.title);
    }

    public void setTitle(int index) {
        title.setText(sTitles[index - 1]);
        icon.setImageResource(itemView.getResources().getIdentifier(
                "ic_looks_" + index, "drawable", itemView.getContext().getPackageName()));
        icon.setImageTintList(R.color.theme_color_primary);
    }
    //对每一种单独type的条目进行初始化
    public static ViewHolderHeader create(ViewGroup parent) {
        return new ViewHolderHeader(LayoutInflater.from(
                parent.getContext()).inflate(R.layout.layout_list_item_header, parent, false));
    }
}

补充知识点

icon.setImageResource(itemView.getResources().getIdentifier("ic_looks_" + index, "drawable", itemView.getContext().getPackageName()));

其中getIdentifier得到的是resId,这个图片的名称是:ic_looks_xxx,其中第二个参数得到的是drawable,raw,id这些类型名称,第三个参数是包名

其中ViewHolderHeader这些是根据type进行不同的配置的原理相同,现在我们就看看里面有什么补充点

1

title.setCompoundDrawablesWithIntrinsicBounds(!isChecked ? R.drawable.selector_lock : R.drawable.selector_unlock, 0, 0, 0);

设置一个图标在此TextView的上下左右四个不同的位置

主题

public class MyApplication extends Application implements ThemeUtils.switchColor {

    @Override
    public void onCreate() {
        super.onCreate();
        //使用MyApplication中实现的接口
        ThemeUtils.setSwitchColor(this);
    }

    //对应switchColor接口中的replaceColorById
    @Override
    public int replaceColorById(Context context, @ColorRes int colorId) {
        //判断是不是默认主题色
        if (ThemeHelper.isDefaultTheme(context)) {
            return context.getResources().getColor(colorId);
        }
        //得到当前主题色
        String theme = getTheme(context);
        if (theme != null) {
            colorId = getThemeColorId(context, colorId, theme);
        }
        return context.getResources().getColor(colorId);
    }

    //对应switchColor接口中的replaceColor,进行更换颜色
    @Override
    public int replaceColor(Context context, @ColorInt int originColor) {
        //默认主题
        if (ThemeHelper.isDefaultTheme(context)) {
            return originColor;
        }
        String theme = getTheme(context);
        int colorId = -1;

        if (theme != null) {
            colorId = getThemeColor(context, originColor, theme);
        }
        return colorId != -1 ? getResources().getColor(colorId) : originColor;
    }

    private String getTheme(Context context) {
        if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_STORM) {
            return "blue";
        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_HOPE) {
            return "purple";
        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_WOOD) {
            return "green";
        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_LIGHT) {
            return "green_light";
        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_THUNDER) {
            return "yellow";
        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_SAND) {
            return "orange";
        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_FIREY) {
            return "red";
        }
        return null;
    }

    //得到colorid
    private
    @ColorRes
    int getThemeColorId(Context context, int colorId, String theme) {
        switch (colorId) {
            case R.color.theme_color_primary:
                return context.getResources().getIdentifier(theme, "color", getPackageName());
            case R.color.theme_color_primary_dark:
                return context.getResources().getIdentifier(theme + "_dark", "color", getPackageName());
            case R.color.theme_color_primary_trans:
                return context.getResources().getIdentifier(theme + "_trans", "color", getPackageName());
        }
        return colorId;
    }

    private
    @ColorRes
    int getThemeColor(Context context, int color, String theme) {
        switch (color) {
            case 0xfffb7299:
                return context.getResources().getIdentifier(theme, "color", getPackageName());
            case 0xffb85671:
                return context.getResources().getIdentifier(theme + "_dark", "color", getPackageName());
            case 0x99f0486c:
                return context.getResources().getIdentifier(theme + "_trans", "color", getPackageName());
        }
        return -1;
    }
}

改变主题入口

//这个方法是TitleBar的方法,弹出一个Dialog
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == R.id.change_theme) {
        CardPickerDialog dialog = new CardPickerDialog();
        dialog.setClickListener(this);
        dialog.show(getSupportFragmentManager(), CardPickerDialog.TAG);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

自定义Dialog CardPickerDialog

比如当前设置pink主题

case R.id.theme_pink:
        mCurrentTheme = ThemeHelper.CARD_SAKURA;
        setImageButtons(mCurrentTheme);
        break;

当点击确定时

if (mClickListener != null) {
    mClickListener.onConfirm(mCurrentTheme);
}
@Override
public void onConfirm(int currentTheme) {
    if (ThemeHelper.getTheme(MainActivity.this) != currentTheme) {
        //当不是当前主题时进行设置,写入sp内
        ThemeHelper.setTheme(MainActivity.this, currentTheme);
        //
        ThemeUtils.refreshUI(MainActivity.this, new ThemeUtils.ExtraRefreshable() {
                    @Override
                    public void refreshGlobal(Activity activity) {
                        //for global setting, just do once
                        if (Build.VERSION.SDK_INT >= 21) {
                            //设置全局的标题栏
                            final MainActivity context = MainActivity.this;
                            ActivityManager.TaskDescription taskDescription =
                                    new ActivityManager.TaskDescription(null, null,
                                            ThemeUtils.getThemeAttrColor(context, android.R.attr.colorPrimary));
                            setTaskDescription(taskDescription);
                            //设置状态栏颜色
                            getWindow().setStatusBarColor(
                                    ThemeUtils.getColorById(context, R.color.theme_color_primary_dark));
                        }
                    }

                    @Override
                    public void refreshSpecificView(View view) {
                    }
                }
        );
        //底部显示一个黑框信息
        View view = findViewById(R.id.snack_layout);
        if (view != null) {
            TextView textView = (TextView) view.findViewById(R.id.content);
            textView.setText(getSnackContent(currentTheme));
            SnackAnimationUtil.with(this, R.anim.snack_in, R.anim.snack_out)
                    .setDismissDelayTime(1000)
                    .setTarget(view)
                    .play();
        }
    }
}

如下核心代码

//得到系统定义的contentview
View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
refreshView(rootView, extraRefreshable);
private static void refreshView(View view, ExtraRefreshable extraRefreshable) {
        if (view == null) return;
        view.destroyDrawingCache();
        //如果View继承了Tintable,则利用tint方法去处理,我们稍后看这个方法如何处理
        if (view instanceof Tintable) {
            ((Tintable) view).tint();
            //当处理后递归遍历该view容器下的其他控件
            if (view instanceof ViewGroup) {
                for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                    refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
                }
            }
        } else {
            //
            if (extraRefreshable != null) {
                extraRefreshable.refreshSpecificView(view);
            }
            if (view instanceof AbsListView) {
                try {
                    //final RecycleBin mRecycler = new RecycleBin();对应拿到这个引用
                    if (sRecyclerBin == null) {
                        sRecyclerBin = AbsListView.class.getDeclaredField("mRecycler");
                        sRecyclerBin.setAccessible(true);
                    }
                    if (sListViewClearMethod == null) {
                        //拿到RecycleBin中的clear方法
                        sListViewClearMethod = Class.forName("android.widget.AbsListView$RecycleBin")
                                .getDeclaredMethod("clear");
                        sListViewClearMethod.setAccessible(true);
                    }
                    //执行clear方法进行所有缓存view的清除
                    sListViewClearMethod.invoke(sRecyclerBin.get(view));
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                //更新每一个视图
                ListAdapter adapter = ((AbsListView) view).getAdapter();
                while (adapter instanceof WrapperListAdapter) {
                    adapter = ((WrapperListAdapter) adapter).getWrappedAdapter();
                }
                if (adapter instanceof BaseAdapter) {
                    ((BaseAdapter) adapter).notifyDataSetChanged();
                }
            }
            if (view instanceof RecyclerView) {
                try {
                    if (sRecycler == null) {
                        sRecycler = RecyclerView.class.getDeclaredField("mRecycler");
                        sRecycler.setAccessible(true);
                    }
                    if (sRecycleViewClearMethod == null) {
                        sRecycleViewClearMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler")
                                .getDeclaredMethod("clear");
                        sRecycleViewClearMethod.setAccessible(true);
                    }
                    sRecycleViewClearMethod.invoke(sRecycler.get(view));
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                ((RecyclerView) view).getRecycledViewPool().clear();
                ((RecyclerView) view).invalidateItemDecorations();
            }
            if (view instanceof ViewGroup) {
                for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                    refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
                }
            }
        }
    }

这样相当于核心又落到了Tintable.tint()方法

我们就先拿TintView说起

public class TintView extends View implements Tintable, AppCompatBackgroundHelper.BackgroundExtensible {
    private AppCompatBackgroundHelper mBackgroundHelper;

    public TintView(Context context) {
        this(context, null);
    }

    public TintView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TintView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //编辑模式就退出
        if (isInEditMode()) {
            return;
        }
        //此时理解成操作类
        TintManager tintManager = TintManager.get(context);//[3.1]
        //利用AppCompatBackgroundHelper组合当前控件和操作类TintManager
        mBackgroundHelper = new AppCompatBackgroundHelper(this, tintManager);//[3.2]
        mBackgroundHelper.loadFromAttribute(attrs, defStyleAttr);//[3.3]
    }
    ...
}

现在我们就开始通过构造入口了解

3.1 TintManager.get(context)

目的很简单就是根据context生成对应的TintManager,然后添加到名称是INSTANCE_CACHE的WeakHashMap弱引用Map中

private static final WeakHashMap<Context, com.bilibili.magicasakura.utils.TintManager> INSTANCE_CACHE = new WeakHashMap<>();

public static com.bilibili.magicasakura.utils.TintManager get(Context context) {
    if (context == null) return null;

    if (context instanceof ContextThemeWrapper) {
        context = ((ContextThemeWrapper) context).getBaseContext();
    }
    if (context instanceof android.view.ContextThemeWrapper) {
        context = ((android.view.ContextThemeWrapper) context).getBaseContext();
    }
    com.bilibili.magicasakura.utils.TintManager tm = INSTANCE_CACHE.get(context);
    if (tm == null) {
        tm = new com.bilibili.magicasakura.utils.TintManager(context);//3.1.1
        INSTANCE_CACHE.put(context, tm);
    }
    return tm;
}

3.1.1 TintManager构造

private TintManager(Context context) {
    mContextRef = new WeakReference<>(context);
}

3.2 new AppCompatBackgroundHelper(this, tintManager)

就是一个存入的功能

public AppCompatBaseHelper(T view, TintManager tintManager) {
    mView = view;
    mTintManager = tintManager;
}

public AppCompatBackgroundHelper(View view, TintManager tintManager) {
    super(view, tintManager);
}

3.3 AppCompatBackgroundHelper.loadFromAttribute()

@SuppressWarnings("ResourceType")
@Override
void loadFromAttribute(AttributeSet attrs, int defStyleAttr) {
    initPadding();//得到view的上下左右padding
    //得到早定义的属性
    TypedArray array = mView.getContext().obtainStyledAttributes(attrs, R.styleable.TintViewBackgroundHelper, defStyleAttr, 0);
    //如果backgroundTint这个参数有值
    if (array.hasValue(R.styleable.TintViewBackgroundHelper_backgroundTint)) {
        //设置值
        mBackgroundTintResId = array.getResourceId(R.styleable.TintViewBackgroundHelper_backgroundTint, 0);
        //之后看backgroundTintMode设置的模式是什么
        if (array.hasValue(R.styleable.TintViewBackgroundHelper_backgroundTintMode)) {
            //先进行解析模式,然后进行设置
            //这里进行[3.3.1],[3.3.2]处理
            setSupportBackgroundTintMode(DrawableUtils.parseTintMode(array.getInt(R.styleable.TintViewBackgroundHelper_backgroundTintMode, 0), null));
        }
        setSupportBackgroundTint(mBackgroundTintResId);//[3.3.3]
    } else {
        //如果没有设置backgroundTint,那就看android_background设置的值,如果设置了就设置背景了
        Drawable drawable = mTintManager.getDrawable(mBackgroundResId = array.getResourceId(R.styleable.TintViewBackgroundHelper_android_background, 0));
        if (drawable != null) {
            setBackgroundDrawable(drawable);
        }
    }
    array.recycle();
}

3.3.1 DrawableUtils.parseTintMode(array.getInt(R.styleable.TintViewBackgroundHelper_backgroundTintMode, 0), null)

返回对应的mode

public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
    switch (value) {
        case 3:
            return PorterDuff.Mode.SRC_OVER;
        case 5:
            return PorterDuff.Mode.SRC_IN;
        case 9:
            return PorterDuff.Mode.SRC_ATOP;
        case 14:
            return PorterDuff.Mode.MULTIPLY;
        case 15:
            return PorterDuff.Mode.SCREEN;
        case 16:
            return Build.VERSION.SDK_INT >= 11 ? PorterDuff.Mode.valueOf("ADD")
                    : defaultMode;
        default:
            return defaultMode;
    }
}

3.3.2

当mode不等于null的时候用一个TintInfo记录,我们需要记住mHasTintMode,mTintMode已经有值此时

private void setSupportBackgroundTintMode(PorterDuff.Mode mode) {
    if (mBackgroundTintResId != 0 && mode != null) {
        if (mBackgroundTintInfo == null) {
            mBackgroundTintInfo = new TintInfo();
        }
        mBackgroundTintInfo.mHasTintMode = true;
        mBackgroundTintInfo.mTintMode = mode;
    }
}

3.3.3 setSupportBackgroundTint()

当设置backgroundTint的时候得到mBackgroundTintResId进行操作

private boolean setSupportBackgroundTint(int resId) {
    if (resId != 0) {
        if (mBackgroundTintInfo == null) {
            mBackgroundTintInfo = new TintInfo();
        }
        mBackgroundTintInfo.mHasTintList = true;
        mBackgroundTintInfo.mTintList = mTintManager.getColorStateList(resId);
    }
    return applySupportBackgroundTint();//[3.3.4]
}

applySupportBackgroundTint()

private boolean applySupportBackgroundTint() {
    Drawable backgroundDrawable = mView.getBackground();
    if (backgroundDrawable != null && mBackgroundTintInfo != null && mBackgroundTintInfo.mHasTintList) {
        backgroundDrawable = DrawableCompat.wrap(backgroundDrawable);
        backgroundDrawable = backgroundDrawable.mutate();//起到单一修改背景的作用,并不会影响其他控件的色
        if (mBackgroundTintInfo.mHasTintList) {
            //对背景设置颜色
            DrawableCompat.setTintList(backgroundDrawable, mBackgroundTintInfo.mTintList);
        }
        if (mBackgroundTintInfo.mHasTintMode) {
            //还可以根据view不同的状态进行着色
            DrawableCompat.setTintMode(backgroundDrawable, mBackgroundTintInfo.mTintMode);
        }
        //看是否backgroundDrawable进行改变
        if (backgroundDrawable.isStateful()) {
            backgroundDrawable.setState(mView.getDrawableState());
        }
        setBackgroundDrawable(backgroundDrawable);
        return true;
    }
    return false;
}

这里参考一片文章:https://race604.com/tint-drawable/

我们通过项目再来举个栗子

  1. 得到一个Item中的条目
public static class ViewHolderHeader extends ViewHolder {
        public static ViewHolderHeader create(ViewGroup parent) {
            return new ViewHolderHeader(LayoutInflater.from(
                    parent.getContext()).inflate(R.layout.layout_list_item_header, parent, false));
        }
}
  1. layout_list_item_header.xml
<com.bilibili.magicasakura.widgets.TintImageView
    android:id="@+id/icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="centerInside"
    android:src="@drawable/ic_adb_white_24dp"
    app:imageTint="@color/theme_color_primary" />
  • 其中scaleType:
    以原图完全显示为目的,将图片的内容完整居中显示,通过按比例缩小原图的size宽(高)等于或小于ImageView的宽(高)
  • 对应:imageTint<color name="theme_color_primary">#fb7299</color>

public class TintImageView extends ImageView implements Tintable, AppCompatBackgroundHelper.BackgroundExtensible,
        AppCompatImageHelper.ImageExtensible {

    public TintImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if (isInEditMode()) {
            return;
        }
        //生成TintManager
        TintManager tintManager = TintManager.get(context);
        //记录当前View与TintManager
        mBackgroundHelper = new AppCompatBackgroundHelper(this, tintManager);//[4.1]
        mBackgroundHelper.loadFromAttribute(attrs, defStyleAttr);//[4.2]

        mImageHelper = new AppCompatImageHelper(this, tintManager);//[4.3]
        mImageHelper.loadFromAttribute(attrs, defStyleAttr);//[4.4]
    }
 ...   
}

其中4.1和4.3都是记录作用就不多说

4.2 AppCompatBackgroundHelper.loadFromAttribute()

这里支持通过backgroundTintMode设置mode

void loadFromAttribute(AttributeSet attrs, int defStyleAttr) {
    initPadding();//获取padding
    TypedArray array = mView.getContext().obtainStyledAttributes(attrs, R.styleable.TintViewBackgroundHelper, defStyleAttr, 0);
    if (array.hasValue(R.styleable.TintViewBackgroundHelper_backgroundTint)) {
        //得到backgroundTint属性的值
        mBackgroundTintResId = array.getResourceId(R.styleable.TintViewBackgroundHelper_backgroundTint, 0);
        if (array.hasValue(R.styleable.TintViewBackgroundHelper_backgroundTintMode)) {
            //得到backgroundTintMode的值并set,然后通过setSupportBackgroundTintMode设置进去
            setSupportBackgroundTintMode(DrawableUtils.parseTintMode(array.getInt(R.styleable.TintViewBackgroundHelper_backgroundTintMode, 0), null));
        }
        setSupportBackgroundTint(mBackgroundTintResId);
    } else {
        Drawable drawable = mTintManager.getDrawable(mBackgroundResId = array.getResourceId(R.styleable.TintViewBackgroundHelper_android_background, 0));
        if (drawable != null) {
            setBackgroundDrawable(drawable);
        }
    }
    array.recycle();
}

首先用TintInfo记录,

private boolean setSupportBackgroundTint(int resId) {
    if (resId != 0) {
        if (mBackgroundTintInfo == null) {
            mBackgroundTintInfo = new TintInfo();
        }
        mBackgroundTintInfo.mHasTintList = true;
        mBackgroundTintInfo.mTintList = mTintManager.getColorStateList(resId);//是上一步配置的backgroundTint
    }
    return applySupportBackgroundTint();//[4.2.1]
}

4.2.1 applySupportBackgroundTint()

private boolean applySupportBackgroundTint() {
    Drawable backgroundDrawable = mView.getBackground();
    if (backgroundDrawable != null && mBackgroundTintInfo != null && mBackgroundTintInfo.mHasTintList) {
        //包装以后可进行着色
        backgroundDrawable = DrawableCompat.wrap(backgroundDrawable);
        backgroundDrawable = backgroundDrawable.mutate();//起到单一修改背景的作用,并不会影响其他控件的色
        if (mBackgroundTintInfo.mHasTintList) {
            //对背景设置颜色
            DrawableCompat.setTintList(backgroundDrawable, mBackgroundTintInfo.mTintList);
        }
        if (mBackgroundTintInfo.mHasTintMode) {
            //还可以根据view不同的状态进行着色
            DrawableCompat.setTintMode(backgroundDrawable, mBackgroundTintInfo.mTintMode);
        }
        if (backgroundDrawable.isStateful()) {
            backgroundDrawable.setState(mView.getDrawableState());
        }
        setBackgroundDrawable(backgroundDrawable);
        return true;
    }
    return false;
}

此时背景色已经设置完成

我们再来回顾一下,到底从哪里开始执行变色

private static void refreshView(View view, ExtraRefreshable extraRefreshable) {
    if (view == null) return;

    view.destroyDrawingCache();
    if (view instanceof Tintable) {
        ((Tintable) view).tint();//看这里

这个方法我们可以查看上面的回顾

TintImageView

@Override
public void tint() {
    if (mBackgroundHelper != null) {
        mBackgroundHelper.tint();//看一个就好
    }
    if (mImageHelper != null) {
        mImageHelper.tint();
    }
}
@Override
public void tint() {
    if (mBackgroundTintResId == 0 || !setSupportBackgroundTint(mBackgroundTintResId)) {
        Drawable drawable = mTintManager.getDrawable(mBackgroundResId);//思想是根据mBackgroundResId得到Drawable
        if (drawable == null) {
            drawable = mBackgroundResId == 0 ? null : ContextCompat.getDrawable(mView.getContext(), mBackgroundResId);
        }
        setBackgroundDrawable(drawable);//设置背景
    }
}

最后我们就明白如何更换主题,我们主要是利用自定义控件,然后通过统一的设置进行更改。

根据项目结构进行大致分析

  • drawables
FilterableStateListDrawable
  • utils
ColorStateListUtils
DrawableInflateDelegate
DrawableUtils
GradientDrawableInflateImpl
InputConnectionImpl
LayerDrawableInflateImpl
RippleDrawableInflateImpl
StateListDrawableInflateImpl
ThemeUtils
TintInfo
TintManager
VectorDrawableInflateImpl
  • widgets
AppCompatBackgroundHelper
AppCompatBaseHelper
AppCompatCompoundButtonHelper
AppCompatCompoundDrawableHelper
AppCompatForegroundHelper
AppCompatImageHelper
AppCompatProgressBarHelper
AppCompatSwitchHelper
AppCompatTextHelper
Tintable
TintAppAlertDialogDividingView
TintAppBarLayout
TintAutoCompleteTextView
TintButton
TintCheckBox
TintCheckedTextView
TintConstraintLayout
TintEditText
TintFrameLayout
TintGridLayout
TintImageView
TintLinearLayout
TintProgressBar
TintProgressDialog
TintRadioButton
TintRelativeLayout
TintSwitchCompat
TintTextView
TintToolbar
TintView

FilterableStateListDrawable

这个类是针对StateListDrawable不能针对单一的状态添加colorFilter设计的

public class FilterableStateListDrawable extends StateListDrawable {

    private int currIdx = -1;
    private int childrenCount = 0;
    private SparseArray<ColorFilter> filterMap;

    public FilterableStateListDrawable() {
        super();
        filterMap = new SparseArray<>();
    }

    @Override
    public void addState(int[] stateSet, Drawable drawable) {
        super.addState(stateSet, drawable);
        childrenCount++;
    }

    public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) {
        if (colorFilter == null) {
            addState(stateSet, drawable);
            return;
        }
        // this is a new custom method, does not exist in parent class
        int currChild = childrenCount;
        addState(stateSet, drawable);
        filterMap.put(currChild, colorFilter);
    }

    @Override
    public boolean selectDrawable(int idx) {

        boolean result = super.selectDrawable(idx);
        // check if the drawable has been actually changed to the one I expect
        if (getCurrent() != null) {
            currIdx = result ? idx : currIdx;
            setColorFilter(getColorFilterForIdx(currIdx));
        } else {
            currIdx = -1;
            setColorFilter(null);
        }
        return result;
    }

    private ColorFilter getColorFilterForIdx(int idx) {
        return filterMap != null ? filterMap.get(idx) : null;
    }

    @Override
    public ConstantState getConstantState() {
        return super.getConstantState();
    }

}

根据一个SparseArray这样就对应每一种状态有一个ColorFilter了。

ColorFilter主要用来处理颜色
- ColorMatrixColorFilter
- LightingColorFilter
- PorterDuffColorFilter

反正都是对图片颜色进行处理

再来看看ColorStateList的作用

/** 
 * 对TextView设置ColorStateList使其在Normal、Pressed、Focused、Unable四种状态下显示不同的颜色。<br/> 
 * StateListDrawable可直接使用图片应用在相似场合。 
 */  
public class ActColorStateList extends Activity implements OnClickListener {  
    private TextView txtShow;  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        txtShow = (TextView) findViewById(R.id.txtShow);  
        txtShow.setText("Sodino\nNormal:0xffffffff\nPressed:0xffffff00\nFocused:0xff0000ff\nUnable:0xffff0000");  
        txtShow.setTextColor(createColorStateList(0xffffffff, 0xffffff00, 0xff0000ff, 0xffff0000));  
        txtShow.setOnClickListener(this);  
    }  

    /** 对TextView设置不同状态时其文字颜色。 */  
    private ColorStateList createColorStateList(int normal, int pressed, int focused, int unable) {  
        int[] colors = new int[] { pressed, focused, normal, focused, unable, normal };  
        int[][] states = new int[6][];  
        states[0] = new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled };  
        states[1] = new int[] { android.R.attr.state_enabled, android.R.attr.state_focused };  
        states[2] = new int[] { android.R.attr.state_enabled };  
        states[3] = new int[] { android.R.attr.state_focused };  
        states[4] = new int[] { android.R.attr.state_window_focused };  
        states[5] = new int[] {};  
        ColorStateList colorList = new ColorStateList(states, colors);  
        return colorList;  
    }  

    /** 设置Selector。 */  
    public static StateListDrawable newSelector(Context context, int idNormal, int idPressed, int idFocused,  
            int idUnable) {  
        StateListDrawable bg = new StateListDrawable();  
        Drawable normal = idNormal == -1 ? null : context.getResources().getDrawable(idNormal);  
        Drawable pressed = idPressed == -1 ? null : context.getResources().getDrawable(idPressed);  
        Drawable focused = idFocused == -1 ? null : context.getResources().getDrawable(idFocused);  
        Drawable unable = idUnable == -1 ? null : context.getResources().getDrawable(idUnable);  
        // View.PRESSED_ENABLED_STATE_SET  
        bg.addState(new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled }, pressed);  
        // View.ENABLED_FOCUSED_STATE_SET  
        bg.addState(new int[] { android.R.attr.state_enabled, android.R.attr.state_focused }, focused);  
        // View.ENABLED_STATE_SET  
        bg.addState(new int[] { android.R.attr.state_enabled }, normal);  
        // View.FOCUSED_STATE_SET  
        bg.addState(new int[] { android.R.attr.state_focused }, focused);  
        // View.WINDOW_FOCUSED_STATE_SET  
        bg.addState(new int[] { android.R.attr.state_window_focused }, unable);  
        // View.EMPTY_STATE_SET  
        bg.addState(new int[] {}, normal);  
        return bg;  
    }  

    @Override  
    public void onClick(View v) {  
        if (v == txtShow) {  
            txtShow.setEnabled(false);  
        }  
    }  
}  

具体使用方法请参考

欢迎关注公众号:码老板
这里写图片描述

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011311586/article/details/79443990
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭