浅谈Android主题款式

文章结尾有附带比如的源码链接, 感兴趣的能够下载源码研讨, 滋味更佳.

在讲Android主题之前, 让咱们先回顾一下Android中自界说View的完成办法.

自界说View

完全自界说View完成自界说控件

自界说View、ViewGroup或许SurfaceView:

  • 自界说View:首要重写onDraw(制作)办法。自界说View完成比如

  • 自界说ViewGroup:首要重写:onMeasure(丈量)、onLayout(布局)这两个办法。自界说ViewGroup完成比如

  • 自界说SurfaceView:创立RenderThread,然后调用SurfaceHolder的.lockCanvas办法获取画布,再调用SurfaceHolder的.unlockCanvasAndPost办法将制作的画布投射到屏幕上。


class CustomSurfaceView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
) : SurfaceView(context, attrs), SurfaceHolder.Callback {
    private var mSurfaceHolder: SurfaceHolder = holder
    private lateinit var mRenderThread: RenderThread
    private var mIsDrawing = false
    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
    override fun surfaceCreated(holder: SurfaceHolder) {
        // 开启RenderThread
        mIsDrawing = true
        mRenderThread = RenderThread()
        mRenderThread.start()
    }
    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // 销毁RenderThread
        mIsDrawing = false
        mRenderThread.interrupt()
    }
    /**
     * 制作界面的线程
     */
    private inner class RenderThread : Thread() {
        override fun run() {
            // 不停制作界面
            while (mIsDrawing) {
                drawUI()
                try {
                    sleep(...) // 改写距离
                } catch (_: InterruptedException) {
                }
            }
        }
    }
    /**
     * 界面制作
     */
    private fun drawUI() {
        val canvas = mSurfaceHolder.lockCanvas()
        try {
            drawCanvas(canvas)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            mSurfaceHolder.unlockCanvasAndPost(canvas)
        }
    }
}

自界说SurfaceView完成比如

承继组件的办法完成自界说控件

最简略的自界说组件的办法,直接承继需要拓宽/修正的控件,重写对应的办法即可。

一般是希望在原有体系控件基础上做一些润饰性的修正(功能增强),而不会做大起伏的改动。

承继组件完成比如

组合的办法完成自界说控件

组合控件便是将多个控件组合成一个新的控件,能够重复运用。

完成组合控件的一般过程如下:

  • 编写布局文件
  • 完成结构办法
  • 初始化UI,加载布局
  • 对外提供修正的接口api

能够看到,组合的办法和咱们平常写一个Fragment的流程是很类似的。

组合组件完成比如

Theme主题

运用于窗体级别,是一整套款式的组合,采取就近准则:Application > Activity > ViewGroup > View。 一般而言,Theme首要运用于Application和Activity这样的窗体,首要放在/res/values/themes.xml

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
</resources>

Application中的Theme

Application的主题一般在Manifest中,它只对在Manifest中未设置Theme的Activity收效。

<application android:theme="@style/AppTheme">
</application>

Activity中的Theme

Activity的主题能够在Manifest和代码中调用setTheme设置。一般在Activity的onCreate()中,setContentView办法之前设置。

1.在Manifest中设置。

<activity android:theme="@style/DialogTheme">
</activity>

2.代码中调用setTheme设置,注意必定要在调用setContentView(View)inflate(int, ViewGroup)办法前。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setTheme(R.style.AppTheme)
    setContentView(R.layout.layout_main)
}

ViewGroup和View中的Theme

ViewGroup和View的主题一般在布局xml中设置,运用android:theme设置。

<ViewGroup
    android:theme="@style/ThemeOverlay.App.Foo">
    <Button android:theme="@style/ThemeOverlay.App.Bar" />
</ViewGroup>

Style款式

仅运用于单个View这种窗体元素级别的外观,首要放在/res/values/styles.xml

Style的声明

款式的声明,一般放在/res/values/...目录下带styles的文件中,运用<style name="style-name"> </style>进行设置。

<style name="style-name" parent="parent-style-name">
    <item name="attr-name1">value1</item>
    <item name="attr-name2">value2</item>
    <item name="attr-name3">value3</item>
</style>

Style的运用

款式一般在布局xml中设置,运用android:style设置,不同于主题,款式只能运用于单个View,对于其子View并不会收效。

<ViewGroup
    android:style="@style/ActionContainerStyle">
    <Button android:style="@style/BlueButtonStyle" />
</ViewGroup>

Style的优先级次序

如果咱们在多个当地给控件指定了style的特点,那么最终是由谁收效呢?这里咱们就以TextView为例,介绍一下Style的收效规则:

  • 1.经过文本span将字符设置的款式运用到TextView派生的类。
  • 2.以代码办法动态设置的特点。
  • 3.将独自的特点直接运用到View。
  • 4.将款式运用到View。
  • 5.控件的默认款式,在View结构办法中界说的。
  • 6.控件所在运用、Activity、父布局所运用的主题。
  • 7.运用某些特定于View的款式,例如为TextView设置TextAppearance。

具体代码可参阅: StyleRuleFragment

Attribute特点

Attribute特点是组成Style的基本单位。如果说主题是各种款式的组合,那么款式便是各种特点的组合,首要放在/res/values/attrs.xml

Attribute的声明

1.单个特点的界说

<resource>
    <attr name="attr-name" format="format-type" />
</resource>

2.一组特点的界说

<resource>
    <declare-styleable name="XXXXView">
        <attr name="attr-name" format="format-type" />
        <attr name="attr-name" format="format-type" />
    </declare-styleable>
</resource>

3.特点的赋值

<style name="xx">
  <item name="attr-name">value</item>
</style>

Attribute的运用

运用?attr/xxx或许?xxx进行引用。这里xxx是界说的特点名(attr-name)。

<TextView
    android:foreground="?attr/selectableItemBackground"
    android:textColor="?colorAccent" />

Attribute的获取

  • 特点集的获取: 运用context.obtainStyledAttributes进行全体获取。
val array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyleAttr, defStyleRes)
size = array.getInteger(R.styleable.CustomTextView_ctv_size, size)
isPassword = array.getBoolean(R.styleable.CustomTextView_ctv_is_password, isPassword)
array.recycle()
  • 单个特点的获取: 运用context.theme.resolveAttribute进行获取。
fun Resources.Theme.resolveAttributeToDimension(@AttrRes attributeId: Int, defaultValue: Float = 0F) : Float {
    val typedValue = TypedValue()
    return if (resolveAttribute(attributeId, typedValue, true)) {
        typedValue.getDimension(resources.displayMetrics)
    } else {
        defaultValue
    }
}
fun Context.resolveDimension(@AttrRes attributeId: Int, defaultValue: Float = 0F) : Float {
    val typedArray = theme.obtainStyledAttributes(intArrayOf(attributeId))
    return try {
        typedArray.getDimension(0, defaultValue)
    } finally {
        typedArray.recycle()
    }
}

最后

以上内容的全部源码我都放在了github上, 感兴趣的小伙伴能够下下来研讨和学习.

项目地址: github.com/xuexiangjys…

我是xuexiangjys,一枚热爱学习,爱好编程,勤于思考,致力于Android架构研讨以及开源项目经历共享的技术up主。获取更多资讯,欢迎微信查找大众号:【我的Android开源之旅】

本文正在参加「金石计划」