深入理解Android 自定义attr Style styleable以及其应用

简介: 相信每一位从事Android开发的猿都遇到过需要自己去自定义View的需求,如果想通过xml指定一些我们自己需要的参数,就需要自己声明一个styleable,并在里面自己定义一些attr属性,这个过程相信大家都比较了解。当然,属性其实也不一定需要和View配合使用,比如我想通过一个Theme中的style对一个库进行一些简单参数的配置,这应该怎么做呢?我今天在封装一个库时


相信每一位从事Android开发的猿都遇到过需要自己去自定义View的需求,如果想通过xml指定一些我们自己需要的参数,就需要自己声明一个styleable,并在里面自己定义一些attr属性,这个过程相信大家都比较了解。当然,属性其实也不一定需要和View配合使用,比如我想通过一个Theme中的style对一个库进行一些简单参数的配置,这应该怎么做呢?我今天在封装一个库时在这个地方浪费了较多时间,最后没办法,到处搜搜资料,记录在这里吧,相信对大家都有帮助。

attr和styleable的关系

首先要明确一点,attr不依赖于styleable,styleable只是为了方便attr的使用
我们自己定义属性完全可以不放到styleable里面,比如直接在resources文件中定义一些属性:

<attr name="custom_attr1" format="string" />
<attr name="custom_attr2" format="string" />
AI 代码解读

定义一个attr就会在R文件里面生成一个Id,那么我们去获取这个属性时,必须调用如下代码:

int[] custom_attrs = {R.attr.custom_attr1,R.custom_attr2};
TypedArray typedArray = context.obtainStyledAttributes(set,custom_attrs);
AI 代码解读

而通过定义一个styleable,我们可以在R文件里自动生成一个int[],数组里面的int就是定义在styleable里面的attr的id。所以我们在获取属性的时候就可以直接使用styleable数组来获取一系列的属性。

  <declare-styleable name="custom_attrs">   
          <attr name="custom_attr1" format="string" />
          <attr name="custom_attr2" format="string" />
  </declare-styleable>
AI 代码解读

获取:

  TypedArray typedArray = context.obtainStyledAttributes(set,R.styleable.custom_attrs);
AI 代码解读

由上面的例子可以知道,定义一个declare-styleable,在获取属性的时候为我们自动提供了一个属性数组。此外,我觉得使用declare-styleable的方式有利于我们我们把相关的属性组织起来,有一个分组的概念,属性的使用范围更加明确。

obtainStyledAttributes函数获取属性

其实我们在前面已经使用了obtainStyledAttributes来获取属性了,现在来看看这个函数的声明吧:

  • obtainAttributes(AttributeSet set, int[] attrs) //从layout设置的属性集中获取attrs中的属性
  • obtainStyledAttributes(int[] attrs) //从系统主题中获取attrs中的属性
  • obtainStyledAttributes(int resId,int[] attrs) //从资源文件定义的style中读取属性
  • obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
    //这是最复杂的一种情况,后面细说。

这么多重载的方法是不是已经看懵了?其实你只需要理解其中的参数就能掌握各个方法的使用方法。所谓获取属性,无非就是需要两个参数:第一,我需要获取那些属性;第二:我从哪里去获取这些属性(数据源)。

attrs

attrs:int[],每个方法中都有的参数,就是告诉系统需要获取那些属性的值。

set

set:表示从layout文件中直接为这个View添加的属性的集合,如:android:layout_width="match_parent"。注意,这里面的属性必然是通过xml配置添加的,也就是由LayoutInflater加载进来的布局或者View`才有这个属性集。

现在你知道为啥我们在自己定义View的时候至少要重写(Context context, AttributeSet set)构造器了吧?因为不重写时,我们将无法获取到layout中配置的属性!!当然,也因为这样,LayoutInflater在inflater布局时会通过反射去调用View的(Context context, AttributeSet attrs)构造器。
set 中实际上又有两种数据来源,当然最后都会包含在set中。一种是直接使用android:layout_width="wrap_content"这种直接指定的,还有一种是通过style="@style/somestyle"这样指定的。

defStyleAttr

这个参数是本文的关键所在,也是自定义一个可以在Theme中配置的样式的关键,先看个栗子吧:
如果我想通过在系统主题里面设置一个样式,修改所有textview的样式,你一般会这么做:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    //在主题中设置textview的style
    <item name="android:textViewStyle">@style/textviewstyle</item>
</style>

<style name="textviewstyle" parent="android:style/Widget.TextView">
    <!--指定一些属性-->
</style>
AI 代码解读

首先android:textViewStyle其实就是一个普通的在资源文件中定义的属性attr,它的format="reference"。那问题来了,TextView是怎么得知我们自己定义的textviewstyle的呢?这其实就是defStyleAttr的应用场景:定义Theme可配置样式。

  public TextView(Context context, AttributeSet attrs) {
    //指定属性textViewStyle为defStyleAttr,然后系统会去搜索Theme中你为这个
    //属性配置的style
    this(context, attrs, com.android.internal.R.attr.textViewStyle);
  }

  public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
    //最终调用到View的第四个构造器时,调用了obtainStyledAttributes
    TypedArray a = theme.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
  }
AI 代码解读

resId=defStyleRes

resId or defStyleRes:直接从资源文件中定义的某个样式中读取。

NULL

看看第二个方法吧,里面除了指定了attrs属性集之外没有任何属性值来源,数据从哪儿来呢?原来我们可以直接在Theme中指定属性的值,那么NULL表示直接从Theme中读取属性。

是不是看到这里你已经有点迷糊了?不要紧,耐心看下去,后面有一个例子,看完例子你再回头看看这里的说明就ok了。

四个参数的obtainStyledAttributes

看看这个方法,返回的结果还是我们所关心的attrs(int[])中包含的属性集。那么数据来源呢?一共有4个,set,defStyleAttr,NULL,defStyleRes,如果一个属性在多个地方都被定义了,那么以哪个为准?

优先级如下:
set>defStyleAttr(主题可配置样式)>defStyleRes(默认样式)>NULL(主题中直接指定)

栗子终于来了!!下载地址-GitHub

attr资源文件中如下定义:

  //定义属性
<declare-styleable name="custom_attrs">
    <attr name="custom_color1" format="color"></attr>
    <attr name="custom_color2" format="color"></attr>
    <attr name="custom_color3" format="color"></attr>
    <attr name="custom_color4" format="color"></attr>
    <attr name="custom_color5" format="color"></attr>
</declare-styleable>
//定义theme可配置style
<attr name="custom_style" format="reference"></attr>
 //定义默认style
<style name="default_style">
    <item name="custom_color1">#ff333333</item>
    <item name="custom_color2">#ff333333</item>
    <item name="custom_color3">#ff333333</item>
    <item name="custom_color4">#ff333333</item>
</style>
AI 代码解读

styles资源文件中如下定义:

  <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
      //配置style
    <item name="custom_style">@style/custom_theme</item>
      //直接在主题中指定
    <item name="custom_color1">#ff444444</item>
    <item name="custom_color2">#ff444444</item>
    <item name="custom_color3">#ff444444</item>
    <item name="custom_color4">#ff444444</item>
    <item name="custom_color5">#ff444444</item>
  </style>
    //主题中配置的style
  <style name="custom_theme">
    <item name="custom_color1">#ff222222</item>
    <item name="custom_color2">#ff222222</item>
    <item name="custom_color3">#ff222222</item>
  </style>
    //直接在layout文件中引用的style,最后会被放到set中
  <style name="myStyle">
    <item name="custom_color1">#ff111111</item>
    <item name="custom_color2">#ff111111</item>
  </style>
AI 代码解读

layout中如下定义:

  <com.exmp.MyCustomView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:style="@style/myStyle"
    app:custom_color1="#ff000000"
    >
  </com.exmp.MyCustomView>
AI 代码解读

在MyCustomView的构造器中:

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

public MyCustomView(Context context, AttributeSet set) {
    this(context, set, R.attr.custom_style);
}

public LinearRecyclerView(Context context, AttributeSet set, int defStyle) {
    super(context, set, defStyle);
    final TypedArray a = context.obtainStyledAttributes(
            set, R.styleable.custom_attrs, defStyle, R.style.default_style);
}
AI 代码解读

如上配置之后,TypedArray中获取的属性值分别是:
custom_color1=#ff000000 //布局文件中直接指定,优先级最高
custom_color2=#ff111111 //布局同通过style指定,也包含在set中,优先级第二
custom_color3=#ff222222 //布局通过主题中配置风格style
custom_color4=#ff444444 //由系统Theme直接指定的
custom_color5=#ff444444

这里看到我们的默认style没有效果,原因是只有当defStyle(Theme中可配置style)不为0 而且在Theme中已经配置了defStyle时,默认style不起效果。

TypedArray

我们看到在获取到属性值之后,都会返回一个TypedArray对象,它又是什么鬼?TypedArray主要有两个作用,第一是内部去转换attrid和属性值数组的关系;第二是提供了一些类型的自动转化,比如我们getString时,如果你是通过@string/hello这种方式设置的,TypedArray会自动去将ResId对应的string从资源文件中读出来。说到底,都是为了方便我们获取属性参数。

例子下载地址-GitHub

回到开始

现在我们应该知道如何为我们的自定义View添加在主题中可配置的Style,主要是通过
obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)方法来做,需要注意的是,defStyleAttrdefStyleRes都可以设置成0表示不去搜索可配置的风格和默认风格。

问题来了,如果来实现我的第二个需求为一个普通的类添加一个可以在Theme中可以配置的样式(主要不就是为了业务方使用库时配置或者传入一些简单的值,这里不去讨论这种方式的优劣,只讨论可行性)?其实很简单:

首先定义:

  <attr name="config_style" reformat="referenc" />

  public class ClassNeedConfig {

    public ClassNeedConfig(Context context) {
         //因为这个类不是通过Inflate进来的,所以肯定没有set,直接给null就ok
         //attrs  你需要获取的属性,通常是自己定义的
         //指定在Theme中搜索的属性
          // 0表示不去搜索默认的样式
        TypedArray a = context.obtainStyledAttributes(null,attrs,R.attr.config_style,0);
    }
}
AI 代码解读
目录
打赏
0
0
0
0
498
分享
相关文章
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
54 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
170 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
104 14
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
100 0
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
411 4
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
68 1
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。

热门文章

最新文章

  • 1
    Android历史版本与APK文件结构
    13
  • 2
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    23
  • 3
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    15
  • 4
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    2
  • 5
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    4
  • 6
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    6
  • 7
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    2
  • 8
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    6
  • 9
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
    4
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    1
  • 1
    android FragmentManager 删除所有Fragment 重建
    18
  • 2
    Android实战经验之Kotlin中快速实现MVI架构
    31
  • 3
    即时通讯安全篇(一):正确地理解和使用Android端加密算法
    36
  • 4
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
    43
  • 5
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    144
  • 6
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    46
  • 7
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    59
  • 8
    Android历史版本与APK文件结构
    161
  • 9
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    48
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    41
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等