#Android源码#自定义UI控件

自定义UI控件

系统是如何定义UI控件的?

1. 首先我们在布局文件中定义了一个布局

我们自定义的布局(其中声明了命名空间为xmlns后面的内容)

<View
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view"
    android:visibility="visible"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

View的属性其实是在attrs.xml中声明的

<?xml version="1.0" encoding="utf-8"?>
<resources>
    ......
    <declare-styleable name="View">
        <attr name="id" format="reference" />
        <attr name="visibility">
            <!-- Visible on screen; the default value. -->
            <enum name="visible" value="0" />
            <!-- Not displayed, but taken into account during layout (space is left for it). -->
            <enum name="invisible" value="1" />
            <!-- Completely hidden, as if the view had not been added. -->
            <enum name="gone" value="2" />
        </attr>
    </declare-styleable>
    ......
</resources>

2. 接着,让我们看看View类是如何获取我们在布局中定义的属性的值

在View.java的构造方法中是这样的

public View(Context context, AttributeSet attrs, int defStyle) {
    this(context);

    TypedArray a = context.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.View, defStyle, 0);

    final int N = a.getIndexCount();
    for (int i = 0; i < N; i++) {
        int attr = a.getIndex(i);
        switch (attr) {
        ......
        case com.android.internal.R.styleable.View_background:
            background = a.getDrawable(attr);
            break;
        case com.android.internal.R.styleable.View_id:
            mID = a.getResourceId(attr, NO_ID);
            break;
        case com.android.internal.R.styleable.View_visibility:
            final int visibility = a.getInt(attr, 0);
            if (visibility != 0) {
                viewFlagValues |= VISIBILITY_FLAGS[visibility];
                viewFlagMasks |= VISIBILITY_MASK;
            }
        ......
        }
        break;
    }
    a.recycle();
}

涉及的一些类

TypedArray

它是这样的一种数据结构,它保有了通过Resources.obtainStyledAttributes或者Resources.obtainAttributes检索到的值的数组,调用完毕务必要调用recycle方法以便重用。它既包含资源、资源id,还包含资源的值。里面大量用到TypedValue的方法来进行资源类型的判定和值的获取

TypedValue

它持有资源的值,具有对资源类型判定和值的获取方法

//int转为float的方法
complexToFloat(int data)
//转换各种尺寸值成像素值
public static float applyDimension(int unit, float value,
                                   DisplayMetrics metrics)
{
    switch (unit) {
    case COMPLEX_UNIT_PX:
        return value;
    case COMPLEX_UNIT_DIP:
        return value * metrics.density;
    case COMPLEX_UNIT_SP:
        return value * metrics.scaledDensity;
    case COMPLEX_UNIT_PT:
        return value * metrics.xdpi * (1.0f/72);
    case COMPLEX_UNIT_IN:
        return value * metrics.xdpi;
    case COMPLEX_UNIT_MM:
        return value * metrics.xdpi * (1.0f/25.4f);
    }
    return 0;
}

Theme

是Resource的一个public final类,是资源属性值的集合。通常和TypedArray连用来解析属性值

其中最常用的一个方法是obtainStyledAttributes,用来获取属性集中指定命名空间的属性

Resource

提供对应用资源的存取方法

//获取当前屏幕的度量,再通过DisplayMetrics获取比例密度
getDisplayMetrics()
//获取AssetManager
getAssets()
//获取指定资源id的颜色
getColor(int id)
//获取指定资源id的尺寸值(dp/sp)
getDimension(int id)
//获取指定资源id的可绘制资源
getDrawable(int id)
//获取指定资源id的字符串数组
getStringArray(int id)
//获取属性集中指定命名空间的属性,封装成TypedArray
obtainAttributes(AttributeSet set, int[] attrs)

R

提供id,以便对res文件夹的资源进行引用
包括attr、dimen、drawable、id、layout、menu、string、style、styleable

玩一把,自定义控件

1. 在res/values/attrs.xml中声明自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="paddingStyle">
            <enum name="small" value="0" />
            <enum name="normal" value="1" />
            <enum name="large" value="2" />
        </attr>
    </declare-styleable>
</resources>

2. 写自定义控件类,继承自View并重写构造方法

package com.example.test;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class MyView extends TextView {

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

3. 在布局中引用自定义的控件并给自定义的属性赋值(注意:要加命名空间)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lshare="http://schemas.android.com/apk/res/com.example.test"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <com.example.test.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Small"
        lshare:paddingStyle="small" />
    <com.example.test.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Normal"
        lshare:paddingStyle="normal" />
    <com.example.test.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Large"
        lshare:paddingStyle="large" />
</LinearLayout>

4. 在自定义控件的构造方法中获取自定义属性的值

  • 从大属性集中获取自定义属性集(包含属性值)
  • 遍历属性集获取属性id
  • 判断id所属再获取对应的值
  • 归还检索的属性集(为了之后再次使用)
package com.example.test;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class MyView extends TextView {

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // android:layout_height="48dp"
        this.setHeight((int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources()
                        .getDisplayMetrics()));
        // android:layout_width="match_parent"
        this.setWidth(context.getResources().getDisplayMetrics().widthPixels);
        // android:background="#330000ff"
        this.setBackgroundColor(Color.parseColor("#330000ff"));
        // obtainStyledAttributes其实是Theme的一个方法,Context里调用了它的方法
        // 传入的参数attrs是属性值的集合
        // 传入的参数R.styleable.ChangeColorIconWithText是要获取的属性的id数组
        // 返回的TypedArray是属性值的集合,要求在调用后,执行recycle方法
        TypedArray a = context
                .obtainStyledAttributes(attrs, R.styleable.MyView);
        // 获取属性的总数
        int count = a.getIndexCount();
        // 使用for+switch循环判断属性名
        // 再通过TypedArray的getDrawable、getColor、getString
        // 或getDimension获取对应的属性值
        for (int i = 0; i < count; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
            case R.styleable.MyView_paddingStyle:
                int paddingStyle = a.getInt(attr, 1);
                switch (paddingStyle) {
                case 0:// small
                    this.setPadding(4, 4, 4, 4);
                    break;
                case 1:// normal
                    this.setPadding(8, 8, 8, 8);
                    break;
                case 2:// large
                    this.setPadding(16, 16, 16, 16);
                    break;
                }
                break;
            }
        }
        //回收利用属性
        a.recycle();
    }
}

运行结果

这里写图片描述

代码打包下载

http://pan.baidu.com/s/1c1k8tJa

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值