关于android:浅谈Android主题样式

1次阅读

共计 4992 个字符,预计需要花费 13 分钟才能阅读完成。

浅谈 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 开源之旅】

正文完
 0