深入了解Android自定义属性美高梅网投平台,自定

2020-03-05 01:15 来源:未知

自定义view的时候,有时需要用到自定义属性,方便我们定制View。一般来说,自定义属性过程如下:

重写onDraw方式

这种方式就是自定义一个继承View的类,重写onDraw方法,之前android中的View分析到调用draw方法绘制一个View时,其中一个步骤是通过onDraw来绘制自身内容,所以重写这个方法就能绘制出自己想要的界面,例如下面的代码:

 @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        mPaint.setColor(Color.BLUE);  
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);  
    }  

这段代码画了一个黄色的矩形,其中getWidth()和getHeight()会在布局流程里确定。
例子的onDraw实现非常简单,而对于界面稍微复杂点的自定义View,onDraw实现的逻辑需要非常复杂,所以一般不用这种方式实现自定义View

转载请标明出处:

译自:

  1. 定义属性:在values下的attrs.xml内编写declare-styleable标签来定义属性;
  2. 使用属性:在布局文件中通过获取
  3. 获取属性:在自定义view中使用TypedArray获取自定义的属性。

组合控件

顾名思义就是几个控件的组合,这些控件往往都是安卓原生系统自带的,例如:
custom.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher"
        android:id="@ id/image"
        android:layout_width="100dp"
        android:layout_height="100dp" />
    <TextView
        android:layout_centerHorizontal="true"
        android:layout_below="@ id/image"
        android:gravity="center"
        android:text="这是安卓的机器人"
        android:id="@ id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:id="@ id/Outer"
    android:layout_height="match_parent"
    tools:context="com.example.lingo.view.MainActivity"
    android:orientation="vertical">
    <TextView
        android:layout_gravity="center_horizontal"
        android:id="@ id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我要插入一个CustomView" />
    <com.example.lingo.view.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </com.example.lingo.view.CustomView>
</LinearLayout>

CustomView.java

public class CustomView extends FrameLayout {
    public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.custom, this);
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
}

运行结果:

美高梅网投平台 1

Screenshot_20170816-193034.png

CustomView继承了FrameLayout,间接继承了ViewGroup,所以会被当成合法的容器控件。在acitivty_main.xml添加CustomView,当调用setContentView(R.layout.layout_main)加载activity_main.xml中的控件时,
构造函数CustomView(@NonNull Context context, @Nullable AttributeSet attrs)会被调用,在这个构造函数内部,通过inflate的方式以这个CustomView为父容器,加载了custom.xml中的控件

http://blog.csdn.net/lmj623565791/article/details/45022631;

曾几何时,我经常对Android View构造函数感到疑惑。为什么有四个构造函数?每个参数做什么?我需要实现哪些构造函数?

下面按照自定义View的流程讲讲自定义属性:首先说明一下自定义View的四个构造函数的区别:

继承已有的View

举个例子,继承已有的ListView,但点击某一行时添加R.layout.custom中的控件到这一行
MyListView.java

public class MyListView extends ListView {
    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }
    public void addMyView(View view){
        ViewGroup vp=(ViewGroup) view;
        View v=LayoutInflater.from(getContext()).inflate(R.layout.custom, null);
        vp.addView(v);
    }
}

custom.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher"
        android:id="@ id/image"
        android:layout_width="100dp"
        android:layout_height="100dp" />
</RelativeLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ArrayList<String> texts=new ArrayList<>();
        texts.add("1");
        texts.add("2");
        texts.add("3");
        final MyListView myListView=(MyListView)findViewById(R.id.mylistView);
        MyAdapter adapter=new MyAdapter(this,android.R.layout.simple_expandable_list_item_1,texts);
        myListView.setAdapter(adapter);
        myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                myListView.addMyView(view);
            }
        });

    }
}

MyAdapter.java

public class MyAdapter extends ArrayAdapter<String> {

    public MyAdapter(Context context, int textViewResourceId, List<String> objects) {
        super(context, textViewResourceId, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(R.layout.list_item, null);
        } else {
            view = convertView;
        }
        TextView textView = (TextView) view.findViewById(R.id.textView);
        textView.setText(getItem(position));
        return view;
    }

}

其实是利用了ListView每一行可以是一个ViewGroup的原则,点击某一行,以这一行为父容器,通过inflate的方式加载R.layout.custom中的控件。

本文出自:【张鸿洋的博客】

TL,DR:

如果你想要快速实用的建议,只需关注以下几点:

       1、使用 单参构造方法 View(Context)在代码中创建Views。

       2、当需要从Xml挂载视图Views的时候重写View(Context, AttributeSet)。

       3、忽略其他你不需要的部分。

注意了,还在车上的人,拉好扶手,开车了。

public class CustomView extends View { public CustomView(Context context) { super; } public CustomView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); }}

自定义View通常还会涉及到自定义属性

1、引言

Constructor parameters 构造函数参数

构造函数最多有四个参数,简单的说,他们就是:

      Context  - 用于视图中的所有位置(推荐持有Activity context);

      AttributeSet  - Xml属性(挂载xml视图);

      int defStyleAttr - 应用于View的默认样式(定义于Theme中);

      int defStyleResource - 如果defStyleAttr未使用,则应用于View的默认样式;

除了Context ,其他的参数仅用于根据Xml属性(布局、样式。主题)配置视图的初始状态。

在使用过程中,一般都是联级调用。第一个构造函数用处并不大,主要在Java代码中声明View时才使用;在布局文件中使用自定义的View,则调用的是第二个构造函数;第三,第四个构造函数是与系统主题有关的,从参数defStyleAttrdefStyleRes也可以看得出来;也就是说,在自定义View时,如果不需要view跟随主题改变,则前两个构造函数就足够了。

声明

一般自定义属性的第一步是在value文件夹下添加一个attrs.xml文件,例如:

 <?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="custom_attr1" format="string" />
        <attr name="custom_attr2" format="string" />
    </declare-styleable>
    <attr name="custom_attr3" format="string" />
</resources>

atrr标签声明属性,name是名称,format是类型,可以是常见的integer、boolean、string、enum、color、dimension、reference等

上面有两种声明自定义属性的形式,一种声明在declare-styleable标签中,一种不在这对标签中,有什么区别呢?先看第二种,第二种能在R.attr类中找到public static final int custom_attr3 = xxx,xxx是id,那第一种呢?当然还会在R.attr类生成在 public static final int custom_attr1=yyy和CustomView_custom_attr2=zzz;除此以外R.styleable类中会生成一个数组public static final int[] CustomView,数组存储的是R.attr的成员 public static final int custom_attr1和CustomView_custom_attr2

所以一般采用第一种,自动打包成一个数组放在R.styleable中,方便使用,除此以外在R.styleable中还会生成CustomView_custom_attr1=0和CustomView_custom_attr2=1,用来表示对应attr在R.styleable.CustomView数组中的位置

对于自定义属性,大家肯定都不陌生,遵循以下几步,就可以实现:

Attributes 属性

我们先来谈谈如何定义有效的Xml属性。看XML中ImageView 基本的属性:

<ImageView 

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/icon"/>

有没有想过 layout_width 、layout_height、src从何而来?当然最开始我是没有想过的,反正拿起来用就行。后来发现要耍女朋友就要买车房,MD,家不在雄安,要来钱就只有提高自己了,扯淡了。当然那些属性不是凭空而来,它实际上是系统通过处理<declare-styleable>来声明的。例如:src 就是这样定义的:

<declare-styleable name = "ImageView">

     <attr name = "src"  format="reference|color"/>

     <!-- ...snipped for brevity...-->

</declare-styleable>

每个声明样式都会为每个属性生成一个R.styleable.[name] 和一个R.styleable.[name]_[attribute]。比如, 上面的src样式 就生成R.styleable.ImageView 和 R.styleable.ImageView_src。

当然那R.styleable.[name] 和一个R.styleable.[name]_[attribute] 都是什么玩意呢?注意了,那就是:R.styleable.[name]就是所有属性资源的数组,系统用它来查找属性值。而每个一个R.styleable.[name]_[attribute]只是该数组中的一个索引,因此,你可以一次性检索所有的属性,然后单独查找每个值。当然你也可以把它想象成一个 cursor,R.styleable.[name] 作为查询列,而R.styleable.[name]_[attribute]就是列索引。

关于更多的declare-styleable ,参见官方文档  the official documentation。

关于AttributeSet

在第二个构造函数中,有个AttributeSet参数,那这个参数有啥用的呢?查看源码,我们可以发现AttributeSet是个接口,该接口用于解析xml布局中的属性。举个例子,假设定义了一个CustomerView,在布局中使用:

<com.example.developmc.customview.CustomView android:layout_width="100dp" android:layout_height="100dp" />

然后我们在构造函数中用如下代码打印AttributeSet中的属性:

public CustomView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); int attributeCount = attrs.getAttributeCount(); for (int i = 0; i < attributeCount; i  ) { String attrName = attrs.getAttributeName; String attrVal = attrs.getAttributeValue; Log.e("AttributeSet:", "attrName = "   attrName   " , attrVal = "   attrVal); } }

打印结果如下:

attrName = layout_width , attrVal = 100.0dipattrName = layout_height , attrVal = 100.0dip

可以看到AttributeSet包含了所有在布局中定义的属性,并且能够按顺序地取得各个属性的name和value。换言之,AttributeSet用于解析View在xml布局中的所有属性的name和value,这也是自定义View要使用第二个构造函数的原因。

细心观察一下,我们在布局中layout_width的值是100dp,但打印出来的值却是100dip,这单位不对呀!抱着疑问,我们修改一下布局:

<com.example.developmc.customview.CustomView android:layout_width="@dimen/dimen_100" android:layout_height="100dp" />

其中dimen_100的定义是:

<dimen name="dimen_100">100dp</dimen>

再次打印,结果如下:

attrName = layout_width , attrVal = @2131165262attrName = layout_height , attrVal = 100.0dip

可以看到layout_width变成了一个奇怪的值@2131165262,这个值是怎么来的呢?我们知道,Android会在R.java中为定义的属性生成资源标识符(一个十六进制的数值)。在app/build/generated/r/debug下找到并打开R.java,找到dimen_100,对应的值是0x7f07004e,将这个数值转为十进制,正好就是“2131165262”!

到这里,我们可以得出结论,当在布局中直接赋值(如:android:layout_width="100dp"),Attribute拿到的值,数值是正确的,但单位可能会不对; 当在布局中为属性赋引用值(如:android:layout_width="@dimen/dimen_100"),Attribute拿到的是该值对应的资源标识符!总的来说,Attribute解析出来的属性值并不能直接使用!不要怕,TypedArray就是用于简化这方面的工作的。

使用

定义好了,接下来就是使用了

定义一个控件CustomView
CustomView.java

package com.example.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;



public class CustomView extends FrameLayout {
    public CustomView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.custom, this);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        String custom_attr1= ta.getString(R.styleable.CustomView_custom_attr1);
        String custom_attr2= ta.getString(R.styleable.CustomView_custom_attr2);
        TextView textView=(TextView)findViewById(R.id.text);
        textView.setText(custom_attr1 "  " custom_attr2);
        ta.recycle();
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:id="@ id/Outer"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_gravity="center_horizontal"
        android:id="@ id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我要插入一个CustomView" />
   <com.example.view.CustomView
       custom:custom_attr1="第一个属性"
       custom:custom_attr2="第二个属性"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
   </com.example.view.CustomView>
</LinearLayout>

自定义属性的命名空间可以设置为如下格式: xmlns:命名空间="http://schemas.android.com/apk/res-auto"
构造函数中的AttributeSet是什么?AttributeSet保存的是CustomView在xml中声明的所有的属性,包括custom_attr1,custom_attr2,layout_width和layout_height,如果有style="@style/somestyle",那么somestyle里面的属性也会被包含。context.obtainAttributes(AttributeSet set, int[] attrs) 从set属性集中获取attrs中的属性,attrs保存的是属性的id,返回一个TypeArray实例例如上面的代码:

TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CustomView);

之后通过ta.getString或者ta.getBoolean或者ta.getInt等等获取TypeArray中第(xxx 1)个的属性,例如代码中的:

String custom_attr1= ta.getString(R.styleable.CustomView_custom_attr1);

虽然Attribute set参数也保存有CustomView在xml中声明的所有属性,当然可以获取自定义的属性,例如 String custom_attr1 = attrs.getAttributeValue(0),0是参数的位置,也就是xml中属性声明的次序-1,但是如果遇到android:text="@string/xxx"这种情况会比较麻烦,获取到的是类似于@2131165234这种。所以TypeArray的出现是为了方便,使用TypeArray即使是写成custom:custom_attr1="@string/xxx"也没问题,自动将string从资源文件strings.xml中读取出来。

最后调用了ta.recycle(),这是为什么呢?
因为程序在运行时维护了一个 TypedArray的池,程序调用时,会向该池中请求一个实例,用完之后,调用 recycle() 方法来释放该实例,从而使其可被复用。

自定义一个CustomView(extends View )类

AttributeSet 属性集

我们上面写的Xml被赋予给View作为AttributeSet 属性集。

通常我们不直接访问AttributeSet,而是使用Theme.obtainStyledAttributes()。这是因为原始属性通常需要解析引用并应用样式。例如,如果你在XML中定义style = @ style / MyStyle,则此方法可解析MyStyle,并将其属性添加到属性集中。最后,我们可以通过getsStyledAttributes()返回一个TypedArray,用它来访问相应属性。

不喜欢看文字,哥也不喜欢,上代码:

public ImageView(Context context, AttributeSet attrs) {

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ImageView, 0, 0);

Drawable src = ta.getDrawable(R.styleable.ImageView_src);

setImageDrawable(src);

ta.recycle();

}

在这种情况下,我们传递两个参数来获取StateedAttributes()。第一个是AttributeSet attrs,来自XML的属性。第二个是数组R.styleable.ImageView,它告诉方法我们要提取哪些属性(以什么顺序)。

如上,我们取得返回的TypedArray,我们现在可以访问各个属性。我们需要使用R.styleable.ImageView_src,以便我们正确地索引数组中的属性。

(回收TypedArray也是非常重要的! 非常重要! 非常重要!  ,3遍了哈,如上,我可是加上了的。。。)

通常您可以一次提取多个属性。事实上,ImageView实现比上面我们看见的复杂得多(因为ImageView本身有更多的需要关注的属性)。

想了解更多,参见官方文档 the official documentation。

关于TypedArray

先来回忆一下,我们是如何同时使用Arrtibute和TypedArray的:首先,新建文件attrs.xml,为自定义View添加一个自定义属性:

<declare-styleable name="CustomView"> <attr name="customWidth" format="dimension"/> </declare-styleable>

然后在布局文件中使用自定义的属性customWidth

<com.example.developmc.customview.CustomView android:layout_width="@dimen/dimen_100" android:layout_height="100dp" app:customWidth="@dimen/dimen_100"/>

最后在构造函数中用TypedArray解析customWidth的value,代码如下:

public CustomView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); int attributeCount = attrs.getAttributeCount(); for (int i = 0; i < attributeCount; i  ) { String attrName = attrs.getAttributeName; String attrVal = attrs.getAttributeValue; Log.e("AttributeSet:", "attrName = "   attrName   " , attrVal = "   attrVal); } TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.CustomView); float width = array.getDimension(R.styleable.CustomView_customWidth,200f); Log.e("width:", "widthVal = " String.valueOf; array.recycle(); }

打印结果是:

attrName = layout_width , attrVal = @2131165262attrName = layout_height , attrVal = 100.0dipattrName = customWidth , attrVal = @2131165262widthVal = 350.0

测试虚拟机的像素是:560dpi,那么通过计算,100dp对应的像素就是350dip通过打印结果,可以看到使用Attribute和TypedArray的区别:如果使用Attribute,拿到的结果并不能直接使用,需要进一步处理;而TypedArray则直接取得了正确的数值,简化了这个步骤。所以在使用自定义属性时,我们总是应该使用TypedArray的方式获取属性值。这里需要注意的一点是:每次使用完TypedArray之后,要记得调用recycle()回收。这是为什么呢?从上述代码我们是通过context.obtainStyledAttributes获取TypeadArray实例的,并不是通过new实例的方式获取的。事实上,TypedArray类,没有公有的构造函数,是一个典型的单例模式,程序在运行时维护一个TypedArray池,使用时,向该池中请求一个实例,用完之后,调用 recycle() 方法来释放该实例,从而使其可被其他模块复用。

defStyleAttr, defStyleRes

自定义View的构造函数如下:

public class CustomTextView extends TextView {
    private static final String TAG = CustomTextView.class.getSimpleName();

    public CustomTextView(Context context) {
        super(context);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
       super(context, attrs);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      }
}

第一个在代码中创建CustomTextView时使用,第二个在xml中添加这个CustomTextView时使用,那么神秘的第三个和第四个构造函数呢? 其实第三个和第四个通常在第二个构造函数中显示调用,那么defStyleAttr, defStyleRes 指的是什么?

先来看这个例子,以下部分参考了这篇文章

step1:在values/attrs.xml中声明自定义属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Customize">
        <attr name="attr_one" format="string" />
        <attr name="attr_two" format="string" />
        <attr name="attr_three" format="string" />
        <attr name="attr_four" format="string" />
    </declare-styleable>
    <attr name="CustomizeStyle" format="reference" />
</resources>

注意CustomizeStyle是一个reference。

step2:在values/styles.xml中声明一个style:

<style name="ThroughStyle">
    <item name="attr_one">attr one from style</item>
    <item name="attr_two">attr two from style</item>
</style>

注意attr_one与attr_two与自定义属性重名。

step3:在styles.xml中,定义一个DefaultCustomizeStyle的style:

 <style name="DefaultCustomizeStyle">
        <item name="attr_one">attr one from defalut style res</item>
        <item name="attr_two">attr two from defalut style res</item>
        <item name="attr_three">attr three from defalut style res</item>  
    </style>

step4:指定某个主题下属性(当然可以是自定义属性)的默认值:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="attr_one">attr one from theme</item>
        <item name="attr_two">attr two from theme</item>
        <item name="attr_three">attr three from theme</item>
        <item name="attr_four">attr four from theme</item>
        <item name="CustomizeStyle">@style/CustomizeStyleInTheme</item>
    </style>

    <style name="CustomizeStyleInTheme">
        <item name="attr_one">attr one from theme reference</item>
        <item name="attr_two">attr two from theme reference</item>
        <item name="attr_three">attr three from theme reference</item>
    </style>
</resources>

step5:使用自定义属性:
custom_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:ad="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.angeldevil.customstyle.CustomTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ad:attr_one="attr one in xml"
        style="@style/ThroughStyle"
        android:text="@string/hello_world" />

</RelativeLayout>

CustomTextView.java

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

public class CustomTextView extends TextView {
     private static final String TAG = CustomTextView.class.getSimpleName();

    public CustomTextView(Context context) {
        super(context);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.CustomizeStyle,R.style.DefaultCustomizeStyle);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

    public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyleAttr, defStyleRes);
        String one = a.getString(R.styleable.Customize_attr_one);
        String two = a.getString(R.styleable.Customize_attr_two);
        String three = a.getString(R.styleable.Customize_attr_three);
        String four = a.getString(R.styleable.Customize_attr_four);
        Log.i(TAG, "one:"   one);
        Log.i(TAG, "two:"   two);
        Log.i(TAG, "three:"   three);
        Log.i(TAG, "four:"   four);
        setText(one "n" two "n" three "n" four "n" a.length());
        a.recycle();
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.custom_main);
    }

}

先来看看TypedArray的另一个构造函数:

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)

第一和第二个参数与前面的obtainStyledAttributes (AttributeSet set, int[] attrs)一样
而第三第四个参数与 public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) 的第三和第四个参数含义相同。

defStyleAttr:一个属性的id,这个属性类型是reference,例如R.attr.CustomizeStyle,必须在当前theme中为defStyleAttr属性赋值,指向一个style,例如将CustomizeStyle属性赋值为@style/DefaultCustomizeStyle,否则会有问题(可以看下面第四个属性的解释),当atts[]中包含的某些属性例如attr_three和attr_four在lxml和style中都没有指定时,会从当前theme中id为defSytleAttr的属性指向的style中查找相应的属性,如果这个参数传入0表示不向当前theme中搜索默认值

defStyleRes:一个style的id,例如代码中的R.style.DefaultCustomizeStyle.但是仅在defStyleAttr为0或defStyleAttr不为0但当前theme中没有为id为defStyleAttr的属性赋值时起作用

上面的例子中,在xml指定了attr_one="attr one in xml",在style=“@style/ThroughStyle"中使用了attr_one和attr_two,所以attrs[]中对应attr_three和attr_four在style和xml中均没有指定,故会在当前theme(AppTheme)中id为defStyleAttr的属性(CustomizeStyle)指向的style(@style/CustomizeStyleInTheme)中找attr_three和attr_four。如果没有找到,会使用theme中指定的attr_three和attr_four默认值,如果还没有就为null。

结果如下:

美高梅网投平台 2

Screenshot_20170817-102943.png

如果不在当前theme下为CustomizeStyle赋值,那么结果会变成这样:

美高梅网投平台 3

Screenshot_20170817-103406.png

attr_one同时在xml、style、defStyleAttr、defStyleRes与theme中直接定义了值,但起作用的是在xml中的定义
attr_two同时在style、defStyleAttr、defStyleRes与theme中直接定义了值,但起用的是在style中的定义
attr_three同时在defStyleAttr、defStyleRes与Theme中直接定义了值,但起作用的是defStyleAttr中的定义
attr_four只在theme中直接定义了值,起作用的是them中的定义

所以如果defStyleAttr不为0且有在当前them中赋值,则优先级是xml>style>defStyleAttr>theme
否则是xml>style>defStyleRes>theme

编写values/attrs.xml,在其中编写styleable和item等标签元素

Theme Attributes 主题属性

旁注,为了完整性:在最后一节中使用 getsStyledAttributes()时,AttributeSet 不是我们获取值的唯一的地方。属性也可以存在于主题中。

对于View的挂载,这个很少起到作用。因为你的主题不应该像src那样设置属性,但是如果您使用getsStyledAttributes()来检索主题属性(这是有用的,但不属于本文的范围),则可以发挥作用。

关于declare-styleable

我们一般在attrs.xml中通过<declare-styleable>标签声明自定义的属性;先看看下面的代码:

<resources> <declare-styleable name="CustomView"> <attr name="customWidth" format="dimension"/> </declare-styleable> <attr name="customHeight" format="dimension"/></resources>

在上述attrs.xml中,我们自定义了两个属性:customWidth和customHeight,其中customHeight没有声明在declare-styleable。运行代码后,在生成的R.java文件中,可以同时看到这两个属性:

public static final class attr { public static final int customWidth=0x7f0100ce; public static final int customHeight=0x7f010001;}

可以看到定义在declare-styleable中与直接用attr定义没有实质的不同,系统都会为我们在R.attr中生成响应的属性。但不同的是,如果声明在declare-styleable中,系统除了在R.java的attr类下生成资源标识符,还会为我们在R.java内的styleable类中生成相关的属性:

public static final class styleable { public static final int[] CustomView = { 0x7f0100ce };}

可以看到customWidth属性对应的标识符0x7f0100ce被保存在styleable内的数组中。如上所示,R.styleable.CustomView是一个int[],而里面的元素正是declare-styleable内声明的元素,这个数组在自定义View的构造函数中获得属性值时会用到。将自定义属性分组声明在declare-styleabe中的作用就是系统会自动为我们生成这些东西,如果不声明在declare-styleable中,我们也可以在需要的时候自己构建这个数组,只是比较麻烦。当我们有多个自定义View需要用到同一个自定义属性时,就不能同时在两个declare-styleabe下声明同一个属性了,这时就可以把这个属性直接定义在attr下了,然后在需要使用时,自己构建数组引用即可。本文就到这里,谢谢各位。

在布局文件中CustomView使用自定义的属性(注意namespace)

Default Style Attribute 默认样式属性

你可能已经注意到我在getsStyledAttributes()中为最后两个参数传入了0。它们实际上是两个资源引用 -  defStyleAttr和defStyleRes。我将重点关注第一个,也就是defStyleAttr。

到目前为止,defStyleAttr是getsStyledAttributes()最令人困惑的参数。根据文档说明:

An attribute in the current theme that contains a reference to a style resource that supplies defaults values for the TypedArray.

wow,真绕口。还是讲人话吧,这是一种能够为某种类型的所有视图定义基本样式的方式。例如,如果要一次修改所有应用程序的TextView,您可以在主题中设置textViewStyle。如果不这样做,那么您必须手动为每个TextView设置样式。

这里以TextView为例,介绍实际工作原理。

首先,它是一个属性(在这种情况下为R.attr.textViewStyle)。这里是Android平台定义textViewStyle的地方:

<resources>

    <declare-styleable name="Theme">

           <!-- ...snip... -->

           <!-- Default TextView style.-->

           <attr name="textViewStyle" format="reference"/>

           <!-- ...etc... -->

  </declare-styleable>

</resources>

在CustomView的构造方法中通过TypedArray获取

再次,我们使用declare-styleable,但这次是定义可以存在于主题中的属性。在这里,我们说textViewStyle是一个引用

也就是说,它的值只是一个对资源的引用。在这种情况下,它应该是一种风格的引用。(怎么感觉拗口,是时候多看看书学习下了......)

接下来我们必须在当前主题中设置textViewStyle。默认的Android主题如下所示:

<resources>

       <declare-styleable name="Theme">

               <attr name="textViewStyle" format="reference"/>

      </declare-styleable >

</resources>

然后,您的Application或activity必须为主题设置,通常通过清单:

<activity

android:name=".MyActivity"

android:theme="@style/Theme" />

现在我们可以在getsStyledAttributes()中使用它:

TypedArray ta = theme.obtainStyledAttributes(attrs, R.styleable.TextView, R.attr.textViewStyle, 0);

最终结果是:没有由AttributeSet定义的任何属性的都使用textViewStyle引用的样式来填充。

除非你是核心人物,否则你不需要知道所有这些实现细节。它之所以在就是方便Android框架在主题中定义各种视图的基本样式。

ps:如果你对上述几个步骤不熟悉,建议先熟悉下,再继续~

Default Style Resource 默认风格资源

defStyleRes比其兄弟姐妹更简单。它只是一种风格资源(即@ style / Widget.TextView)。

defStyleRes中样式的属性只有在defStyleAttr未定义(0或未在主题中设置)时才会应用。

那么,我有几个问题:

Precedence 优先

现在我们已经有一堆通过getsStyledAttributes()来获取属性的值方法。这是他们的优先顺序,从最高到最低:

1、AttributeSet中定义的任何值。

2、AttributeSet中定义的样式资源(即style = @ style / blah)。

3、由defStyleAttr指定的默认样式属性。

4、由defStyleResource指定的默认样式资源(如果没有指定defStyleAttr)。

5、主题中设置的值

换言之,您在xml中直接设置的任何属性将首先使用。但是如果你不自己设置的话,这些属性可以从其他地方检索到。

以上步骤是如何奏效的?

View constructors 构造函数

这篇文章应该是关于View构造函数的,对吧?

共有四个,每个增加一个参数:

View(Context)

View(Context, AttributeSet)

View(Context, AttributeSet, defStyleAttr)

View(Context, AttributeSet, defStyleAttr, defStyleRes)

一个重要的注意事项:最后一个添加到API 21中,所以除非minSdkVersion == 21,否则现在应该避免它。 (如果你想使用defStyleRes,只要自己调用getsStyledAttributes(),因为它始终被支持。)

他们级联,所以如果你调用一个,你最终调用他们所有(通过super)。级联也意味着你只需要重写你使用的构造函数。一般来说,这意味着你只需要实现前两个(一个用于代码构造函数,另一个用于挂载XML视图)。

自定义view,通常如下:

MyView(Context c){

this(context, null);

}

MyView(Context  c, AttributeSet attrs){

super(c, attrs);

//相关处理

}

在双参数构造函数中,您可以以任何姿势使用obtainStyledAttributes()。实现默认样式的一种快速方法是只向其提供defStyleRes;这样你就不需要经过在对接defstyleattr的繁琐。

无论如何,我希望这不仅有助于您了解视图构造函数,而且还可以在视图构建过程中检索属性!

坐到这里的都是真爱啊! 下车了,老司机!

styleable 的含义是什么?可以不写嘛?我自定义属性,我声明属性就好了,为什么一定要写个styleable呢?

如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?

构造方法中的有个参数叫做AttributeSet

(eg: MyTextView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的数组,那么我能不能通过它去获取我的自定义属性呢?

TypedArray是什么鬼?从哪冒出来的,就要我去使用?

恩,针对这几个问题,大家可以考虑下,如何回答呢?还是说:老子会背上述4个步骤就够了~~

2、常见的例子

接下来通过例子来回答上述问题,问题的回答顺序不定~~大家先看一个常见的例子,即上述几个步骤的代码化。

自定义属性的声明文件

1

2

3

4

5

6

7

8

9

自定义View类

packagecom.example.test;importandroid.content.Context;importandroid.content.res.TypedArray;importandroid.util.AttributeSet;importandroid.util.Log;importandroid.view.View;publicclassMyTextViewextendsView{privatestaticfinalString TAG = MyTextView.class.getSimpleName();publicMyTextView(Context context, AttributeSet attrs) {super(context, attrs);        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);        String text = ta.getString(R.styleable.test_testAttr);inttextAttr = ta.getInteger(R.styleable.test_text, -1);        Log.e(TAG,"text = " text " , textAttr = " textAttr);        ta.recycle();    }}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

布局文件中使用

1

2

3

4

5

6

7

8

9

10

11

12

13

ok,大家花3s扫一下,运行结果为:

MyTextView: text = helloworld , textAttr =520

1

应该都不意外吧,注意下,我的styleable的name写的是test,所以说这里并不要求一定是自定义View的名字。

3、AttributeSet与TypedArray

下面考虑:

构造方法中的有个参数叫做AttributeSet(eg: MyTextView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的集合,那么我能不能通过它去获取我的自定义属性呢?

首先AttributeSet中的确保存的是该View声明的所有的属性,并且外面的确可以通过它去获取(自定义的)属性,怎么做呢?

其实看下AttributeSet的方法就明白了,下面看代码。

publicMyTextView(Context context, AttributeSet attrs) {super(context, attrs);intcount = attrs.getAttributeCount();for(inti =0; i < count; i ) {            String attrName = attrs.getAttributeName(i);            String attrVal = attrs.getAttributeValue(i);            Log.e(TAG,"attrName = " attrName " , attrVal = " attrVal);        }// ==>use typedarray ...}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

输出:

MyTextView(4136): attrName = layout_width , attrVal =100.0dipMyTextView(4136): attrName = layout_height , attrVal =200.0dipMyTextView(4136): attrName = text , attrVal = helloworldMyTextView(4136): attrName = testAttr , attrVal =520

1

2

3

4

5

结合上面的布局文件,你发现了什么?

我擦,果然很神奇,真的获得所有的属性,恩,没错,通过AttributeSet可以获得布局文件中定义的所有属性的key和value(还有一些方法,自己去尝试),那么是不是说TypedArray这个鬼可以抛弃了呢?答案是:NO!。

现在关注下一个问题:

TypedArray是什么鬼?从哪冒出来的,就要我去使用?

我们简单修改下,布局文件中的MyTextView的属性。

1

2

3

4

5

现在再次运行的结果是:

MyTextView(4692): attrName = layout_width , attrVal = @2131165234MyTextView(4692): attrName = layout_height , attrVal = @2131165235MyTextView(4692): attrName = text , attrVal = @2131361809MyTextView(4692): attrName = testAttr , attrVal =520>>use typedarrayMyTextView(4692): text = Hello world! , textAttr =520

1

2

3

4

5

6

发现了什么?通过AttributeSet获取的值,如果是引用都变成了@ 数字的字符串。你说,这玩意你能看懂么?那么你看看最后一行使用TypedArray获取的值,是不是瞬间明白了什么。

TypedArray其实是用来简化我们的工作的,比如上例,如果布局中的属性的值是引用类型(比如:@dimen/dp100),如果使用AttributeSet去获得最终的像素值,那么需要第一步拿到id,第二步再去解析id。而TypedArray正是帮我们简化了这个过程。

贴一下:如果通过AttributeSet获取最终的像素值的过程:

intwidthDimensionId =  attrs.getAttributeResourceValue(0, -1);        Log.e(TAG,"layout_width= " getResources().getDimension(widthDimensionId));

1

2

3

ok,现在别人问你TypedArray存在的意义,你就可以告诉他了。

4、declare-styleable

我们已经解决了两个问题,接下来,我们看看布局文件,我们有一个属性叫做:zhy:text。

总所周知,系统提供了一个属性叫做:android:text,那么我觉得直接使用android:text更nice,这样的话,考虑问题:

如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?

答案是可以的,怎么做呢?

直接在attrs.xml中使用android:text属性。

1

2

3

4

注意,这里我们是使用已经定义好的属性,不需要去添加format属性(注意声明和使用的区别,差别就是有没有format)。

然后在类中这么获取:ta.getString(R.styleable.test_android_text);布局文件中直接android:text="@string/hello_world"即可。

这里提一下,系统中定义的属性,其实和我们自定义属性的方式类似,你可以在sdk/platforms/android-xx/data/res/values该目录下看到系统中定义的属性。然后你可以在系统提供的View(eg:TextView)的构造方法中发现TypedArray获取属性的代码(自己去看一下)。

ok,接下来,我在想,既然declare-styleable这个标签的name都能随便写,这么随意的话,那么考虑问题:

styleable 的含义是什么?可以不写嘛?我自定义属性,我声明属性就好了,为什么一定要写个styleable呢?

其实的确是可以不写的,怎么做呢?

首先删除declare-styleable的标签

那么现在的attrs.xml为:

1

2

3

4

哟西,so清爽~

* MyTextView实现

packagecom.example.test;importandroid.content.Context;importandroid.content.res.TypedArray;importandroid.util.AttributeSet;importandroid.util.Log;importandroid.view.View;publicclassMyTextViewextendsView{privatestaticfinalString TAG = MyTextView.class.getSimpleName();privatestaticfinalint[] mAttr = { android.R.attr.text, R.attr.testAttr };privatestaticfinalintATTR_ANDROID_TEXT =0;privatestaticfinalintATTR_TESTATTR =1;publicMyTextView(Context context, AttributeSet attrs) {super(context, attrs);// ==>use typedarrayTypedArray ta = context.obtainStyledAttributes(attrs, mAttr);        String text = ta.getString(ATTR_ANDROID_TEXT);inttextAttr = ta.getInteger(ATTR_TESTATTR, -1);//输出 text = Hello world! , textAttr = 520Log.e(TAG,"text = " text " , textAttr = " textAttr);        ta.recycle();    }}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

美高梅网投平台,30

31

32

貌似多了些代码,可以看到我们声明了一个int数组,数组中的元素就是我们想要获取的attr的id。并且我们根据元素的在数组中的位置,定义了一些整形的常量代表其下标,然后通过TypedArray进行获取。

可以看到,我们原本的:

R.styleable.test => mAttr

R.styleable.test_text => ATTR_ANDROID_TEXT(0)

R.styleable.test_testAttr => ATTR_TESTATTR(1)

1

2

3

那么其实呢?android在其内部也会这么做,按照传统的写法,它会在R.java生成如下代码:

publicstaticfinalclassattr{publicstaticfinalinttestAttr=0x7f0100a9;    }publicstaticfinalclassstyleable{publicstaticfinalinttest_android_text =0;publicstaticfinalinttest_testAttr =1;publicstaticfinalint[] test = {0x0101014f,0x7f0100a9};    }

ok,根据上述你应该发现了什么。styleale的出现系统可以为我们完成很多常量(int[]数组,下标常量)等的编写,简化我们的开发工作(想想如果一堆属性,自己编写常量,你得写成什么样的代码)。那么大家肯定还知道declare-styleable的name属性,一般情况下写的都是我们自定义View的类名。主要为了直观的表达,该declare-styleable的属性,都是改View所用的。

其实了解该原理是有用的,详见:Android 自定义控件 优雅实现元素间的分割线

ok,现在5个问题,回答了4个,第一个问题:

自定义属性的几个步骤是如何奏效的?

恩,上述以及基本涵盖了这个问题的答案,大家自己总结,所以:略。

总结下今天的博客。

attrs.xml里面的declare-styleable以及item,android会根据其在R.java中生成一些常量方便我们使用(aapt干的),本质上,我们可以不声明declare-styleable仅仅声明所需的属性即可。

我们在View的构造方法中,可以通过AttributeSet去获得自定义属性的值,但是比较麻烦,而TypedArray可以很方便的便于我们去获取。

我们在自定义View的时候,可以使用系统已经定义的属性。

注意:1.在自定义的MyTextView内至少有这个构造方法:public MyTextView(Context context, AttributeSet attrs) {super(context, attrs);};因为在使用的时候默认是调用这个构造方法的;也可以加上另外两个构造方法public MyTextView(Context context) {

super(context);

}和public MyTextView(Context context, AttributeSet attrs,intdefStyleAttr) {};这个时候一般在第三个方法里去执行public MyTextView(Context context, AttributeSet attrs) {super(context, attrs);}这里面的方法,此时要修改为public MyTextView(Context context, AttributeSet attrs) {this(context, attrs,0);};如果需要对view进行赋值等修改操作时,需要在TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);之前findviewbyid找到相应的view,否则报空指针异常

TAG标签:
版权声明:本文由美高梅网投平台发布于美高梅网投网址,转载请注明出处:深入了解Android自定义属性美高梅网投平台,自定