Android自定义组件(五)

1.组件的属性

自定义的组件继承自View后就会具备若干的默认属性。

除了View 的默认属性之外、我们也可以为组件自定义属性,自定义属性应遵循以下步骤:

1)在 res/values/attrs.xml 文件中为指定组件定义 declare-styleable 标记, 并将所有的属性
都定义在该标记中;(若没有,自行创建)
2)在 layout 文件中使用自定义属性;
3)在组件类的构造方法中读取属性值。

example:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CirclePhotoView">
        <attr name="text" format="string" />
    </declare-styleable>
</resources>

 一般来讲,定义好属性的名称和类型之后,属性就可以使用了,但是在布局文件中,要注意属性的命名空间(namespace),默认的命名空间是“Android”,是由语句

xmlns:android="http://schemas.android.com/apk/res/android"

所决定的,但是对于自定义属性,必须定义其他的命名空间,

xmlns:app="http://schemas.android.com/apk/res-auto"

其中,“app”是可以使用其他的进行替代,但是后面"http://schemas.android.com/apk/res-auto"则是固定的,当有了这个命名空间之后,之前的自定义属性便可以对其进行使用了。当组件运行之后,所有的属性都将保存在AttributeSet集合中并通过构造方法进行传入,我们可以通过TypedArray读取指定的属性值。

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CirclePhotoView);
        text = typedArray.getString(R.styleable.CirclePhotoView_text);
        typedArray.recycle();

2.读取来自style和theme中的属性

关于组件的属性,可以在四个地方进行定义:

1)组件中定义

2) 组件的style属性中进行定义

3)theme中进行定义

4)theme的style的属性中进行定义

关于四个地方获取属性的方法,首先我们在attr.xml中定义如下四个属性:

<resources>
    <declare-styleable name="CirclePhotoView">
        <attr name="attr1" format="string" />
        <attr name="attr2" format="string" />
        <attr name="attr3" format="string" />
        <attr name="attr4" format="string" />
    </declare-styleable>
    <attr name="myStyle" format="reference" />
</resources>

其中,“myStyle”类型为reference,用于theme的style属性。

下面四个属性将应用于不同的场合,在布局文件中:

    <com.example.hu.customizationview.CirclePhotoView
        android:id="@+id/myview"
        style="@style/viewStyle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        app:attr1="你好啊  你是谁啊" />

在res/values/styles.xml中:

<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="attr3">3你说啥 我不知道</item>
        <item name="myStyle">@style/myDefaultStyle</item>
    </style>

    <style name="myDefaultStyle">
        <item name="attr4">这个是4</item>
    </style>

    <style name="viewStyle">
        <item name="attr2">你真的不知道这个是2?</item>
    </style>

</resources>

最后在CirclePhotoView对属性值进行获取:

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePhotoView,
                defStyleAttr, R.style.myDefaultStyle);
        String attr1 = a.getString(R.styleable.CirclePhotoView_attr1);
        String attr2 = a.getString(R.styleable.CirclePhotoView_attr2);
        String attr3 = a.getString(R.styleable.CirclePhotoView_attr3);
        String attr4 = a.getString(R.styleable.CirclePhotoView_attr4);

在上面的构造方法中,我们调用了

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)。在该方法中:

1)set:属性值的集合。
2)attrs:我们要获取的属性的资源 ID 的一个数组, 我们定义了 attr1、 attr2、 attr3 和 attr4, 这
4 个属性自动生成的索引会存储到 R.styleable.AttrView 数组中, 该数组就是 attrs 参数。
3)defStyleAttr:当前 Theme 中 style 属性, 如果组件和组件的 style 属性都没有为 View 指定属
性时,将从 Theme 的 Style 中查找相应的属性值。
4)defStyleRes:指向一个 Style 的资源 ID,但是仅在 defStyleAttr 为 0 或 defStyleAttr 不为 0 但
Theme 中没有为 defStyleAttr 属性赋值时起作用。

 在下面的图中展示了attr属性的读取顺序:

 

3.ViewGroup常用方法

 在ViewGroup中定义了一个View[ ]类型的数组mChildren,该数组保存了容器中所有子组件,负责维护组件的添加、移除、管理组件顺序等功能。而另一个成员mChildrenCount则保存了容器中子组件的数量。在布局文件中,容器中的子元素会根据顺序自动添加到mChilren数组中。

ViewGroup常用的基本方法有:

(1)public int getChildCount()
          获取容器内的子组件的个数;
(2)public View getChildAt(int index)
         容器内的所有子组件都存储在名为 mChildren 的 View[]数组中, 该方法通过索引 index找到指定位置的子组件;
(3)public void addView(View child, int index, LayoutParams params)
         向容器中添加新的子组件, child 表示子组件(也可以是子容器), index 表示索引, 指定组件所在的位置,                       params 参数为组件指定布局参数, 该方法还有两个简化的版本:
         public void addView(View child, LayoutParams params):
         添加 child 子组件, 并为该子组件指定布局参数;
         public void addView(View child, int index):
         布局参数使用默认的 ViewGroup.LayoutParams,其中 layout_width 和 layout_height 均为 wrap_content;
(4)public void addView(View child): 布局参数同上, 但 index 为-1, 表示将 child 组件添加
         到 mChildren 数组的最后。向容器中添加新的子组件时,子组件不能有父容器,否则会抛出“The specified child                   already has a parent(该组件已有父容器) ”的异常。
(5)public void removeViewAt(int index)
         移除 index 位置的子组件, 类似的方法还有:
         public void removeView(View view):
         移除子组件 view;
         public void removeViews(int start, int count):
         移除从 start 开始连续的 count 个子组件。
(6)protected void measureChild(View child, int parentWidthMeasureSpec, intparentHeightMeasureSpec)
         测量子组件的尺寸。 类似的方法还有:
         protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec):
         测量所有子组件的尺寸;
         public final void measure(int widthMeasureSpec, int heightMeasureSpec):
         该方法从 View类 中 继 承 , 用 于 测 量 组 件 或 容 器 自 己 的 尺 寸 , 参 数 widthMeasureSpec                                       heightMeasureSpec 为 0 时表示按实际大小进行测量, 将 0 传入方法常常会有奇效。

ViewGroup的运行的基本流程大致为:

1) 测量容器尺寸
    重写 onMeasure()方法测量容器大小,和自定义组件有所区别的是, 在测量容器大小之
    前, 必须先调用 measureChildren()方法测量所有子组件的大小,不然结果永远为 0。
2) 确定每个子组件的位置
    重写 onLayout()方法确定每个子组件的位置(这个其实挺麻烦, 也是定义容器的难点部
    分), 在 onLayout()方法中, 调用 View 的 layout()方法确定子组件的位置。
3) 绘制容器
    重写 onDraw()方法,其实 ViewGroup 类并没有重写 onDraw()方法,除非有特别的要求,
    自定义容器也很少去重写。 比如 LinearLayout 重写了该方法用于绘制水平或垂直分割
    条, 而 FrameLayout 则是重写了 draw()方法, 作用其实是一样的。

 在ViewGroup中需要重写的有onLayout方法,该方法的原型为:

protected void onLayout(boolean changed, int l, int t, int r, int b)

其中changed判断是由有新的位置和大小,而l、r、t、b分别代表的意思如图所示:

下面是一个简单的ViewGroup的例子,用来了解关于ViewGroup的重写方法:

public class MyViewGroup extends ViewGroup {


    public MyViewGroup(Context context) {
        this(context, null, 0);
    }

    public MyViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //创建一个按钮
        TextView textView = new TextView(context);
        ViewGroup.LayoutParams layoutParams =
                new ViewGroup.LayoutParams(200, 200);
        textView.setText("Android");
        textView.setBackgroundColor(Color.YELLOW);
        //在当前容器中添加子组件
        this.addView(textView, layoutParams);
        //设置容器背景颜色
        this.setBackgroundColor(Color.alpha(255));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //设置子组件(此处为 TextView)的位置和大小
        View textView = this.getChildAt(0);//只有一个组件,索引为 0
        textView.layout(50, 50, textView.getMeasuredWidth() + 50,
                textView.getMeasuredHeight() + 50);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //先测量所有子组件的大小
        this.measureChildren(widthMeasureSpec, heightMeasureSpec);
        //测量自身的大小,此处直接写死为 500	*	500
        this.setMeasuredDimension(500, 500);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //为容器画一个红色边框
        RectF rect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
        rect.inset(2, 2);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(2);
        paint.setColor(Color.BLACK);
        Path path = new Path();
        path.addRoundRect(rect, 20, 20, Path.Direction.CCW);
        canvas.drawPath(path, paint);
        super.onDraw(canvas);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值