搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > 码老板 > 带你过一遍Android 多主题框架——MagicaSakura

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

码老板 2018-03-13
举报

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

  • 列表内容

  • 列表内容

  • 支持白天彩色主题和夜间主题。

  • 切换主题不需要重建activity

Github官网
首先进入Main的布局

 1<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
2    xmlns:app="http://schemas.android.com/apk/res-auto"
3    android:layout_width="match_parent"
4    android:layout_height="match_parent"
5    android:fitsSystemWindows="true">

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

14
15        <TextView
16            android:layout_width="wrap_content"
17            android:layout_height="match_parent"
18            android:gravity="center_vertical"
19            android:paddingLeft="@dimen/padding"
20            android:text="@string/app_name"
21            android:textColor="@color/white"
22            android:textSize="22sp" />

23
24    </com.bilibili.magicasakura.widgets.TintToolbar>
25
26    <android.support.v7.widget.RecyclerView
27        android:id="@+id/recycler"
28        android:layout_width="match_parent"
29        android:layout_height="wrap_content"
30        android:layout_marginTop="56dp" />

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

对应Main怎么进入入口

 1//设置title
2Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
3setSupportActionBar(toolbar);
4getSupportActionBar().setTitle(null);
5
6//设置中间竖直可滑动部分
7RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler);
8LinearLayoutManager layoutManager = new LinearLayoutManager(this);
9layoutManager.setSmoothScrollbarEnabled(true);
10
11
12//进行分割线的操作
13recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
14    @Override
15    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
16        //8dp
17        final int padding = getResources().getDimensionPixelOffset(R.dimen.padding_half);
18        //返回适配器条目的位置
19        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
20        final int position = layoutParams.getViewLayoutPosition();
21        if (position == 0) {
22            outRect.left = outRect.top = outRect.right = padding;
23            outRect.bottom = padding >> 1;
24        } else if (position == state.getItemCount() - 1) {
25            outRect.left = outRect.bottom = outRect.right = padding;
26            outRect.top = padding >> 1;
27        } else {
28            outRect.left = outRect.right = padding;
29            outRect.top = outRect.bottom = padding >> 1;
30        }
31    }
32});
33
34//设置进行水平还是垂直
35recyclerView.setLayoutManager(layoutManager);
36
37//设置适配器
38Adapter adapter = new Adapter();
39recyclerView.setAdapter(adapter);
40
41//适配器添加HolderType
42adapter.addViewHolderType(
43        ViewHolder.VIEW_HOLDER_HEADER,
44        ViewHolder.VIEW_HOLDER_LABEL,
45        ViewHolder.VIEW_HOLDER_HEADER,
46        ViewHolder.VIEW_HOLDER_LOGIN,
47        ViewHolder.VIEW_HOLDER_HEADER,
48        ViewHolder.VIEW_HOLDER_DOWNLOAD
49);
这里明白一个知识点
  1. 关于Rect outRect这个参数

1recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
2            @Override
3            public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
4            }
5}

在这个参数中Rect outRect这个矩形控制条目的显示

  • outRect.left : 控制条目距离左边的距离

  • outRect.right:条目距离右边的距离

  • outRect.top : 条目距离上边的距离

  • outRect.bottom : 条目距离下边的距离

  1. 关于padding >> 1

相当于 padding/2的值

接下来我们就看看适配器
 1public static class Adapter extends RecyclerView.Adapter<ViewHolder> {
2    List<Integer> viewHolderTypes = new ArrayList<>();//视图类型
3    SparseArrayCompat<Integer> titleIndexs = new SparseArrayCompat<>();//标题集合
4
5    @Override
6    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
7        return ViewHolder.create(parent, viewType);根据类型创建每一个条目
8    }
9
10    @Override
11    public void onBindViewHolder(ViewHolder holder, int position) {
12        //将数据绑定到item视图上
13        if (holder instanceof ViewHolderHeader) {
14            ((ViewHolderHeader) holder).setTitle(titleIndexs.get(position));
15        }
16    }
17
18    //条目集合数
19    @Override
20    public int getItemCount() {
21        return viewHolderTypes.size();
22    }
23
24    //根据位置得到type信息
25    @Override
26    public int getItemViewType(int position) {
27        return viewHolderTypes.get(position);
28    }
29
30    //
31    public void addViewHolderType(int... type) {
32        for (int i = 0; i < type.length; i++) {
33            if (type[i] == ViewHolder.VIEW_HOLDER_HEADER) {
34                titleIndexs.put(i, titleIndexs.size() + 1);
35            }
36            viewHolderTypes.add(type[i]);
37        }
38        notifyDataSetChanged();
39    }
40}

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

 1//onCreateViewHolder()负责为Item创建视图
2@Override
3public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
4    return null;
5}
6//onBindViewHolder()负责将数据绑定到Item的视图上
7@Override
8public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
9//条目个数
10@Override
11public int getItemCount() {
12    return 0;
13}

根据type创建对应的ViewHolder

 1public static abstract class ViewHolder extends RecyclerView.ViewHolder {
2    public static final int VIEW_HOLDER_HEADER = 0;
3    public static final int VIEW_HOLDER_LABEL = VIEW_HOLDER_HEADER + 1;
4    public static final int VIEW_HOLDER_LOGIN = VIEW_HOLDER_LABEL + 1;
5    public static final int VIEW_HOLDER_DOWNLOAD = VIEW_HOLDER_LOGIN + 1;
6
7    public ViewHolder(View itemView) {
8        super(itemView);
9    }
10
11    public static ViewHolder create(ViewGroup viewHolder, int type) {
12        switch (type) {
13            case VIEW_HOLDER_HEADER:
14                return ViewHolderHeader.create(viewHolder);//[1.1]
15            case VIEW_HOLDER_LABEL:
16                return ViewHolderLabel.create(viewHolder);
17            case VIEW_HOLDER_LOGIN:
18                return ViewHolderLogin.create(viewHolder);
19            case VIEW_HOLDER_DOWNLOAD:
20                return ViewHolderChoice.create(viewHolder);
21            default:
22                return null;
23        }
24    }
25}

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

 1public static class ViewHolderHeader extends ViewHolder {
2    private static final String[] sTitles = new String[]{"Label", "Login", "Choice"};
3    TintImageView icon;
4    TextView title;
5
6    public ViewHolderHeader(View itemView) {
7        super(itemView);
8        icon = (TintImageView) itemView.findViewById(R.id.icon);
9        title = (TextView) itemView.findViewById(R.id.title);
10    }
11
12    public void setTitle(int index) {
13        title.setText(sTitles[index - 1]);
14        icon.setImageResource(itemView.getResources().getIdentifier(
15                "ic_looks_" + index, "drawable", itemView.getContext().getPackageName()));
16        icon.setImageTintList(R.color.theme_color_primary);
17    }
18    //对每一种单独type的条目进行初始化
19    public static ViewHolderHeader create(ViewGroup parent) {
20        return new ViewHolderHeader(LayoutInflater.from(
21                parent.getContext()).inflate(R.layout.layout_list_item_header, parent, false));
22    }
23}

补充知识点

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

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

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

1

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

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

主题

 1public class MyApplication extends Application implements ThemeUtils.switchColor {
2
3    @Override
4    public void onCreate() {
5        super.onCreate();
6        //使用MyApplication中实现的接口
7        ThemeUtils.setSwitchColor(this);
8    }
9
10    //对应switchColor接口中的replaceColorById
11    @Override
12    public int replaceColorById(Context context, @ColorRes int colorId) {
13        //判断是不是默认主题色
14        if (ThemeHelper.isDefaultTheme(context)) {
15            return context.getResources().getColor(colorId);
16        }
17        //得到当前主题色
18        String theme = getTheme(context);
19        if (theme != null) {
20            colorId = getThemeColorId(context, colorId, theme);
21        }
22        return context.getResources().getColor(colorId);
23    }
24
25    //对应switchColor接口中的replaceColor,进行更换颜色
26    @Override
27    public int replaceColor(Context context, @ColorInt int originColor) {
28        //默认主题
29        if (ThemeHelper.isDefaultTheme(context)) {
30            return originColor;
31        }
32        String theme = getTheme(context);
33        int colorId = -1;
34
35        if (theme != null) {
36            colorId = getThemeColor(context, originColor, theme);
37        }
38        return colorId != -1 ? getResources().getColor(colorId) : originColor;
39    }
40
41    private String getTheme(Context context) {
42        if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_STORM) {
43            return "blue";
44        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_HOPE) {
45            return "purple";
46        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_WOOD) {
47            return "green";
48        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_LIGHT) {
49            return "green_light";
50        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_THUNDER) {
51            return "yellow";
52        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_SAND) {
53            return "orange";
54        } else if (ThemeHelper.getTheme(context) == ThemeHelper.CARD_FIREY) {
55            return "red";
56        }
57        return null;
58    }
59
60    //得到colorid
61    private
62    @ColorRes
63    int getThemeColorId(Context context, int colorId, String theme) {
64        switch (colorId) {
65            case R.color.theme_color_primary:
66                return context.getResources().getIdentifier(theme, "color", getPackageName());
67            case R.color.theme_color_primary_dark:
68                return context.getResources().getIdentifier(theme + "_dark", "color", getPackageName());
69            case R.color.theme_color_primary_trans:
70                return context.getResources().getIdentifier(theme + "_trans", "color", getPackageName());
71        }
72        return colorId;
73    }
74
75    private
76    @ColorRes
77    int getThemeColor(Context context, int color, String theme) {
78        switch (color) {
79            case 0xfffb7299:
80                return context.getResources().getIdentifier(theme, "color", getPackageName());
81            case 0xffb85671:
82                return context.getResources().getIdentifier(theme + "_dark", "color", getPackageName());
83            case 0x99f0486c:
84                return context.getResources().getIdentifier(theme + "_trans", "color", getPackageName());
85        }
86        return -1;
87    }
88}

改变主题入口

 1//这个方法是TitleBar的方法,弹出一个Dialog
2@Override
3public boolean onOptionsItemSelected(MenuItem item) {
4    if (item.getItemId() == R.id.change_theme) {
5        CardPickerDialog dialog = new CardPickerDialog();
6        dialog.setClickListener(this);
7        dialog.show(getSupportFragmentManager(), CardPickerDialog.TAG);
8        return true;
9    }
10    return super.onOptionsItemSelected(item);
11}

自定义Dialog CardPickerDialog

1

比如当前设置pink主题

1case R.id.theme_pink:
2        mCurrentTheme = ThemeHelper.CARD_SAKURA;
3        setImageButtons(mCurrentTheme);
4        break;

当点击确定时

1if (mClickListener != null) {
2    mClickListener.onConfirm(mCurrentTheme);
3}
 1@Override
2public void onConfirm(int currentTheme) {
3    if (ThemeHelper.getTheme(MainActivity.this) != currentTheme) {
4        //当不是当前主题时进行设置,写入sp内
5        ThemeHelper.setTheme(MainActivity.this, currentTheme);
6        //
7        ThemeUtils.refreshUI(MainActivity.this, new ThemeUtils.ExtraRefreshable() {
8                    @Override
9                    public void refreshGlobal(Activity activity) {
10                        //for global setting, just do once
11                        if (Build.VERSION.SDK_INT >= 21) {
12                            //设置全局的标题栏
13                            final MainActivity context = MainActivity.this;
14                            ActivityManager.TaskDescription taskDescription =
15                                    new ActivityManager.TaskDescription(null, null,
16                                            ThemeUtils.getThemeAttrColor(context, android.R.attr.colorPrimary));
17                            setTaskDescription(taskDescription);
18                            //设置状态栏颜色
19                            getWindow().setStatusBarColor(
20                                    ThemeUtils.getColorById(context, R.color.theme_color_primary_dark));
21                        }
22                    }
23
24                    @Override
25                    public void refreshSpecificView(View view) {
26                    }
27                }
28        );
29        //底部显示一个黑框信息
30        View view = findViewById(R.id.snack_layout);
31        if (view != null) {
32            TextView textView = (TextView) view.findViewById(R.id.content);
33            textView.setText(getSnackContent(currentTheme));
34            SnackAnimationUtil.with(this, R.anim.snack_in, R.anim.snack_out)
35                    .setDismissDelayTime(1000)
36                    .setTarget(view)
37                    .play();
38        }
39    }
40}

如下核心代码

1//得到系统定义的contentview
2View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
3refreshView(rootView, extraRefreshable);
 1private static void refreshView(View view, ExtraRefreshable extraRefreshable) {
2        if (view == null) return;
3        view.destroyDrawingCache();
4        //如果View继承了Tintable,则利用tint方法去处理,我们稍后看这个方法如何处理
5        if (view instanceof Tintable) {
6            ((Tintable) view).tint();
7            //当处理后递归遍历该view容器下的其他控件
8            if (view instanceof ViewGroup) {
9                for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
10                    refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
11                }
12            }
13        } else {
14            //
15            if (extraRefreshable != null) {
16                extraRefreshable.refreshSpecificView(view);
17            }
18            if (view instanceof AbsListView) {
19                try {
20                    //final RecycleBin mRecycler = new RecycleBin();对应拿到这个引用
21                    if (sRecyclerBin == null) {
22                        sRecyclerBin = AbsListView.class.getDeclaredField("mRecycler");
23                        sRecyclerBin.setAccessible(true);
24                    }
25                    if (sListViewClearMethod == null) {
26                        //拿到RecycleBin中的clear方法
27                        sListViewClearMethod = Class.forName("android.widget.AbsListView$RecycleBin")
28                                .getDeclaredMethod("clear");
29                        sListViewClearMethod.setAccessible(true);
30                    }
31                    //执行clear方法进行所有缓存view的清除
32                    sListViewClearMethod.invoke(sRecyclerBin.get(view));
33                } catch (NoSuchFieldException e) {
34                    e.printStackTrace();
35                } catch (ClassNotFoundException e) {
36                    e.printStackTrace();
37                } catch (NoSuchMethodException e) {
38                    e.printStackTrace();
39                } catch (InvocationTargetException e) {
40                    e.printStackTrace();
41                } catch (IllegalAccessException e) {
42                    e.printStackTrace();
43                }
44                //更新每一个视图
45                ListAdapter adapter = ((AbsListView) view).getAdapter();
46                while (adapter instanceof WrapperListAdapter) {
47                    adapter = ((WrapperListAdapter) adapter).getWrappedAdapter();
48                }
49                if (adapter instanceof BaseAdapter) {
50                    ((BaseAdapter) adapter).notifyDataSetChanged();
51                }
52            }
53            if (view instanceof RecyclerView) {
54                try {
55                    if (sRecycler == null) {
56                        sRecycler = RecyclerView.class.getDeclaredField("mRecycler");
57                        sRecycler.setAccessible(true);
58                    }
59                    if (sRecycleViewClearMethod == null) {
60                        sRecycleViewClearMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler")
61                                .getDeclaredMethod("clear");
62                        sRecycleViewClearMethod.setAccessible(true);
63                    }
64                    sRecycleViewClearMethod.invoke(sRecycler.get(view));
65                } catch (NoSuchMethodException e) {
66                    e.printStackTrace();
67                } catch (IllegalAccessException e) {
68                    e.printStackTrace();
69                } catch (NoSuchFieldException e) {
70                    e.printStackTrace();
71                } catch (InvocationTargetException e) {
72                    e.printStackTrace();
73                } catch (ClassNotFoundException e) {
74                    e.printStackTrace();
75                }
76                ((RecyclerView) view).getRecycledViewPool().clear();
77                ((RecyclerView) view).invalidateItemDecorations();
78            }
79            if (view instanceof ViewGroup) {
80                for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
81                    refreshView(((ViewGroup) view).getChildAt(i), extraRefreshable);
82                }
83            }
84        }
85    }

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

我们就先拿TintView说起

 1public class TintView extends View implements Tintable, AppCompatBackgroundHelper.BackgroundExtensible {
2    private AppCompatBackgroundHelper mBackgroundHelper;
3
4    public TintView(Context context) {
5        this(context, null);
6    }
7
8    public TintView(Context context, AttributeSet attrs) {
9        this(context, attrs, 0);
10    }
11
12    public TintView(Context context, AttributeSet attrs, int defStyleAttr) {
13        super(context, attrs, defStyleAttr);
14        //编辑模式就退出
15        if (isInEditMode()) {
16            return;
17        }
18        //此时理解成操作类
19        TintManager tintManager = TintManager.get(context);//[3.1]
20        //利用AppCompatBackgroundHelper组合当前控件和操作类TintManager
21        mBackgroundHelper = new AppCompatBackgroundHelper(this, tintManager);//[3.2]
22        mBackgroundHelper.loadFromAttribute(attrs, defStyleAttr);//[3.3]
23    }
24    ...
25}

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

3.1 TintManager.get(context)

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

 1private static final WeakHashMap<Context, com.bilibili.magicasakura.utils.TintManager> INSTANCE_CACHE = new WeakHashMap<>();
2
3public static com.bilibili.magicasakura.utils.TintManager get(Context context) {
4    if (context == null) return null;
5
6    if (context instanceof ContextThemeWrapper) {
7        context = ((ContextThemeWrapper) context).getBaseContext();
8    }
9    if (context instanceof android.view.ContextThemeWrapper) {
10        context = ((android.view.ContextThemeWrapper) context).getBaseContext();
11    }
12    com.bilibili.magicasakura.utils.TintManager tm = INSTANCE_CACHE.get(context);
13    if (tm == null) {
14        tm = new com.bilibili.magicasakura.utils.TintManager(context);//3.1.1
15        INSTANCE_CACHE.put(context, tm);
16    }
17    return tm;
18}

3.1.1 TintManager构造

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

3.2 new AppCompatBackgroundHelper(this, tintManager)

就是一个存入的功能

1public AppCompatBaseHelper(T view, TintManager tintManager) {
2    mView = view;
3    mTintManager = tintManager;
4}
5
6public AppCompatBackgroundHelper(View view, TintManager tintManager) {
7    super(view, tintManager);
8}

3.3 AppCompatBackgroundHelper.loadFromAttribute()

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

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

返回对应的mode

 1public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
2    switch (value) {
3        case 3:
4            return PorterDuff.Mode.SRC_OVER;
5        case 5:
6            return PorterDuff.Mode.SRC_IN;
7        case 9:
8            return PorterDuff.Mode.SRC_ATOP;
9        case 14:
10            return PorterDuff.Mode.MULTIPLY;
11        case 15:
12            return PorterDuff.Mode.SCREEN;
13        case 16:
14            return Build.VERSION.SDK_INT >= 11 ? PorterDuff.Mode.valueOf("ADD")
15                    : defaultMode;
16        default:
17            return defaultMode;
18    }
19}

3.3.2

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

1private void setSupportBackgroundTintMode(PorterDuff.Mode mode) {
2    if (mBackgroundTintResId != 0 && mode != null) {
3        if (mBackgroundTintInfo == null) {
4            mBackgroundTintInfo = new TintInfo();
5        }
6        mBackgroundTintInfo.mHasTintMode = true;
7        mBackgroundTintInfo.mTintMode = mode;
8    }
9}

3.3.3 setSupportBackgroundTint()

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

 1private boolean setSupportBackgroundTint(int resId) {
2    if (resId != 0) {
3        if (mBackgroundTintInfo == null) {
4            mBackgroundTintInfo = new TintInfo();
5        }
6        mBackgroundTintInfo.mHasTintList = true;
7        mBackgroundTintInfo.mTintList = mTintManager.getColorStateList(resId);
8    }
9    return applySupportBackgroundTint();//[3.3.4]
10}

applySupportBackgroundTint()

 1private boolean applySupportBackgroundTint() {
2    Drawable backgroundDrawable = mView.getBackground();
3    if (backgroundDrawable != null && mBackgroundTintInfo != null && mBackgroundTintInfo.mHasTintList) {
4        backgroundDrawable = DrawableCompat.wrap(backgroundDrawable);
5        backgroundDrawable = backgroundDrawable.mutate();//起到单一修改背景的作用,并不会影响其他控件的色
6        if (mBackgroundTintInfo.mHasTintList) {
7            //对背景设置颜色
8            DrawableCompat.setTintList(backgroundDrawable, mBackgroundTintInfo.mTintList);
9        }
10        if (mBackgroundTintInfo.mHasTintMode) {
11            //还可以根据view不同的状态进行着色
12            DrawableCompat.setTintMode(backgroundDrawable, mBackgroundTintInfo.mTintMode);
13        }
14        //看是否backgroundDrawable进行改变
15        if (backgroundDrawable.isStateful()) {
16            backgroundDrawable.setState(mView.getDrawableState());
17        }
18        setBackgroundDrawable(backgroundDrawable);
19        return true;
20    }
21    return false;
22}

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

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

  1. 得到一个Item中的条目

1public static class ViewHolderHeader extends ViewHolder {
2        public static ViewHolderHeader create(ViewGroup parent) {
3            return new ViewHolderHeader(LayoutInflater.from(
4                    parent.getContext()).inflate(R.layout.layout_list_item_header, parent, false));
5        }
6}
  1. layout_list_item_header.xml

1<com.bilibili.magicasakura.widgets.TintImageView
2    android:id="@+id/icon"
3    android:layout_width="wrap_content"
4    android:layout_height="wrap_content"
5    android:scaleType="centerInside"
6    android:src="@drawable/ic_adb_white_24dp"
7    app:imageTint="@color/theme_color_primary" />

  • 其中scaleType: 以原图完全显示为目的,将图片的内容完整居中显示,通过按比例缩小原图的size宽(高)等于或小于ImageView的宽(高)

  • 对应:imageTint<color name="theme_color_primary">#fb7299</color>

  1. 3.

 1public class TintImageView extends ImageView implements Tintable, AppCompatBackgroundHelper.BackgroundExtensible,
2        AppCompatImageHelper.ImageExtensible
{
3
4    public TintImageView(Context context, AttributeSet attrs, int defStyleAttr) {
5        super(context, attrs, defStyleAttr);
6        if (isInEditMode()) {
7            return;
8        }
9        //生成TintManager
10        TintManager tintManager = TintManager.get(context);
11        //记录当前View与TintManager
12        mBackgroundHelper = new AppCompatBackgroundHelper(this, tintManager);//[4.1]
13        mBackgroundHelper.loadFromAttribute(attrs, defStyleAttr);//[4.2]
14
15        mImageHelper = new AppCompatImageHelper(this, tintManager);//[4.3]
16        mImageHelper.loadFromAttribute(attrs, defStyleAttr);//[4.4]
17    }
18 ...  
19}

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

4.2 AppCompatBackgroundHelper.loadFromAttribute()

这里支持通过backgroundTintMode设置mode

 1void loadFromAttribute(AttributeSet attrs, int defStyleAttr) {
2    initPadding();//获取padding
3    TypedArray array = mView.getContext().obtainStyledAttributes(attrs, R.styleable.TintViewBackgroundHelper, defStyleAttr, 0);
4    if (array.hasValue(R.styleable.TintViewBackgroundHelper_backgroundTint)) {
5        //得到backgroundTint属性的值
6        mBackgroundTintResId = array.getResourceId(R.styleable.TintViewBackgroundHelper_backgroundTint, 0);
7        if (array.hasValue(R.styleable.TintViewBackgroundHelper_backgroundTintMode)) {
8            //得到backgroundTintMode的值并set,然后通过setSupportBackgroundTintMode设置进去
9            setSupportBackgroundTintMode(DrawableUtils.parseTintMode(array.getInt(R.styleable.TintViewBackgroundHelper_backgroundTintMode, 0), null));
10        }
11        setSupportBackgroundTint(mBackgroundTintResId);
12    } else {
13        Drawable drawable = mTintManager.getDrawable(mBackgroundResId = array.getResourceId(R.styleable.TintViewBackgroundHelper_android_background, 0));
14        if (drawable != null) {
15            setBackgroundDrawable(drawable);
16        }
17    }
18    array.recycle();
19}

首先用TintInfo记录,

 1private boolean setSupportBackgroundTint(int resId) {
2    if (resId != 0) {
3        if (mBackgroundTintInfo == null) {
4            mBackgroundTintInfo = new TintInfo();
5        }
6        mBackgroundTintInfo.mHasTintList = true;
7        mBackgroundTintInfo.mTintList = mTintManager.getColorStateList(resId);//是上一步配置的backgroundTint
8    }
9    return applySupportBackgroundTint();//[4.2.1]
10}

4.2.1 applySupportBackgroundTint()

 1private boolean applySupportBackgroundTint() {
2    Drawable backgroundDrawable = mView.getBackground();
3    if (backgroundDrawable != null && mBackgroundTintInfo != null && mBackgroundTintInfo.mHasTintList) {
4        //包装以后可进行着色
5        backgroundDrawable = DrawableCompat.wrap(backgroundDrawable);
6        backgroundDrawable = backgroundDrawable.mutate();//起到单一修改背景的作用,并不会影响其他控件的色
7        if (mBackgroundTintInfo.mHasTintList) {
8            //对背景设置颜色
9            DrawableCompat.setTintList(backgroundDrawable, mBackgroundTintInfo.mTintList);
10        }
11        if (mBackgroundTintInfo.mHasTintMode) {
12            //还可以根据view不同的状态进行着色
13            DrawableCompat.setTintMode(backgroundDrawable, mBackgroundTintInfo.mTintMode);
14        }
15        if (backgroundDrawable.isStateful()) {
16            backgroundDrawable.setState(mView.getDrawableState());
17        }
18        setBackgroundDrawable(backgroundDrawable);
19        return true;
20    }
21    return false;
22}

此时背景色已经设置完成

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

1private static void refreshView(View view, ExtraRefreshable extraRefreshable) {
2    if (view == null) return;
3
4    view.destroyDrawingCache();
5    if (view instanceof Tintable) {
6        ((Tintable) view).tint();//看这里

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

TintImageView

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

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

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

  • drawables

1FilterableStateListDrawable
  • utils

 1ColorStateListUtils
2DrawableInflateDelegate
3DrawableUtils
4GradientDrawableInflateImpl
5InputConnectionImpl
6LayerDrawableInflateImpl
7RippleDrawableInflateImpl
8StateListDrawableInflateImpl
9ThemeUtils
10TintInfo
11TintManager
12VectorDrawableInflateImpl
  • widgets

 1AppCompatBackgroundHelper
2AppCompatBaseHelper
3AppCompatCompoundButtonHelper
4AppCompatCompoundDrawableHelper
5AppCompatForegroundHelper
6AppCompatImageHelper
7AppCompatProgressBarHelper
8AppCompatSwitchHelper
9AppCompatTextHelper
10Tintable
11TintAppAlertDialogDividingView
12TintAppBarLayout
13TintAutoCompleteTextView
14TintButton
15TintCheckBox
16TintCheckedTextView
17TintConstraintLayout
18TintEditText
19TintFrameLayout
20TintGridLayout
21TintImageView
22TintLinearLayout
23TintProgressBar
24TintProgressDialog
25TintRadioButton
26TintRelativeLayout
27TintSwitchCompat
28TintTextView
29TintToolbar
30TintView

FilterableStateListDrawable

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

 1public class FilterableStateListDrawable extends StateListDrawable {
2
3    private int currIdx = -1;
4    private int childrenCount = 0;
5    private SparseArray<ColorFilter> filterMap;
6
7    public FilterableStateListDrawable() {
8        super();
9        filterMap = new SparseArray<>();
10    }
11
12    @Override
13    public void addState(int[] stateSet, Drawable drawable) {
14        super.addState(stateSet, drawable);
15        childrenCount++;
16    }
17
18    public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) {
19        if (colorFilter == null) {
20            addState(stateSet, drawable);
21            return;
22        }
23        // this is a new custom method, does not exist in parent class
24        int currChild = childrenCount;
25        addState(stateSet, drawable);
26        filterMap.put(currChild, colorFilter);
27    }
28
29    @Override
30    public boolean selectDrawable(int idx) {
31
32        boolean result = super.selectDrawable(idx);
33        // check if the drawable has been actually changed to the one I expect
34        if (getCurrent() != null) {
35            currIdx = result ? idx : currIdx;
36            setColorFilter(getColorFilterForIdx(currIdx));
37        } else {
38            currIdx = -1;
39            setColorFilter(null);
40        }
41        return result;
42    }
43
44    private ColorFilter getColorFilterForIdx(int idx) {
45        return filterMap != null ? filterMap.get(idx) : null;
46    }
47
48    @Override
49    public ConstantState getConstantState() {
50        return super.getConstantState();
51    }
52
53}

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

ColorFilter主要用来处理颜色

  • ColorMatrixColorFilter

  • LightingColorFilter

  • PorterDuffColorFilter

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

更详细的精彩,请点击原文阅读


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《带你过一遍Android 多主题框架——MagicaSakura》的版权归原作者「码老板」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注码老板微信公众号

码老板微信公众号:gh_e8b40c5d0c8e

码老板

手机扫描上方二维码即可关注码老板微信公众号

码老板最新文章

精品公众号随机推荐

举报