浅谈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上, 感兴趣的小伙伴能够下下来钻研和学习.

我的项目地址: https://github.com/xuexiangjys/UIThemeSample

我是xuexiangjys,一枚酷爱学习,喜好编程,勤于思考,致力于Android架构钻研以及开源我的项目教训分享的技术up主。获取更多资讯,欢送微信搜寻公众号:【我的Android开源之旅】