android中自定义ViewGroup的实现

在android中提供了常见的几种ViewGroup的实现,包括LinearLayout、Relativeayout、FrameLayout等。这些ViewGroup可以满足我们一般的开发需求,但是对于界面要求复杂的,这几个布局就显得捉襟见肘了。所以自定义的ViewGroup在我们接触过的应用中比比皆是。

要想实现一个自定义的ViewGroup,第一步是学会自定义属性,这些自定义的属性将让我们配置布局文件的时候更加的灵活。自定义属性是在value目录下声明一个attrs.xml文件。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CascadeViewGroup">
        <attr name="verticalspacing" format="dimension"/>
        <attr name="horizontalspacing" format="dimension"/>
    </declare-styleable>

    <declare-styleable name="CascadeViewGroup_LayoutParams">
        <attr name="layout_paddingleft" format="dimension"/>
        <attr name="layout_paddinTop" format="dimension"/>
    </declare-styleable>
</resources>

在这里我们声明了两个自定义属性集,CascadeViewGroup中的属性是针对我们自定义的CascadeViewGroup组件设置的,也就是可以在布局文件中<CascadeViewGroup>标签中可以使用的属性。另外一个CascadeViewGroup_LayoutParams则是针对于CascadeViewGroup中的子View设置的属性。

在编写代码前,我们还设置了一个默认的宽度和高度供CascadeLayout使用。这两个属性在dimens.xml定义。

<?xml version="1.0" encoding="utf-8"?>
<resources>
      <dimen name="default_horizontal_spacing">10dp</dimen>
    <dimen name="default_vertical_spacing">10dp</dimen>
</resources>

下面开始编写自定义的组件CascadeLayout了。

package com.app.CustomViewMotion;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by charles on 2015/8/13.
 */
public class CascadeViewGroup extends ViewGroup {

    //自定义布局中设置的宽度和高度
    private int mHoriztonalSpacing;
    private int mVerticalSpacing;

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

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

    public CascadeViewGroup(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeViewGroup);
        try {
            //获取设置的宽度
            mHoriztonalSpacing = a.getDimensionPixelSize(R.styleable.CascadeViewGroup_horizontalspacing,
                    this.getResources().getDimensionPixelSize(R.dimen.default_horizontal_spacing));
            //获取设置的高度
            mVerticalSpacing = a.getDimensionPixelSize(R.styleable.CascadeViewGroup_verticalspacing,
                    this.getResources().getDimensionPixelSize(R.dimen.default_vertical_spacing));

        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            a.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = this.getChildCount();
        int width = this.getPaddingLeft();
        int height = this.getPaddingTop();
        for (int i = 0; i < count; i++) {
            final View currentView = this.getChildAt(i);
            this.measureChild(currentView, widthMeasureSpec, heightMeasureSpec);
            CascadeViewGroup.LayoutParams lp = (CascadeViewGroup.LayoutParams) currentView.getLayoutParams();
            if(lp.mSettingPaddingLeft != 0){
                width +=lp.mSettingPaddingLeft;
            }
            if(lp.mSettingPaddingTop != 0){
                height +=lp.mSettingPaddingTop;
            }
            lp.x = width;
            lp.y = height;
            width += mHoriztonalSpacing;
            height += mVerticalSpacing;
        }
        width +=getChildAt(this.getChildCount() - 1).getMeasuredWidth() + this.getPaddingRight();
        height += getChildAt(this.getChildCount() - 1).getMeasuredHeight() + this.getPaddingBottom();
        this.setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec));

    }

    @Override
    protected void onLayout(boolean b, int l, int i1, int i2, int i3) {
        final int count = this.getChildCount();
        for (int i = 0; i < count; i++) {
            final View currentView = this.getChildAt(i);
            CascadeViewGroup.LayoutParams lp = (CascadeViewGroup.LayoutParams) currentView.getLayoutParams();
            currentView.layout(lp.x, lp.y, lp.x + currentView.getMeasuredWidth(),
                    lp.y + currentView.getMeasuredHeight());
        }

    }

    public static class LayoutParams extends ViewGroup.LayoutParams {
        int x;
        int y;
        int mSettingPaddingLeft;
        int mSettingPaddingTop;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CascadeViewGroup_LayoutParams);
            mSettingPaddingLeft = a.getDimensionPixelSize(R.styleable.CascadeViewGroup_LayoutParams_layout_paddingleft, 0);
            mSettingPaddingTop = a.getDimensionPixelSize(R.styleable.CascadeViewGroup_LayoutParams_layout_paddinTop, 0);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(this.getContext(), attrs);
    }
}

代码稍微优点长,但是结构还是很清晰的。

1)构造方法中或者XML文件中配置属性的值。通过TypedArray中的方法获取我们在layout布局中设置的属性,并且将他们保存在成员变量中。

2)构造自定义的内部类LayoutParams。构造这个内部类,可以方便我们在测量我们的子View的时候保存他们的属性值,以便在Layout阶段布局。

3)generateLayoutParams()、generateDefaultParams()等方法。在这些方法中返回我们自定义的layoutParams。至于为什么要重写这些方法,可以查看ViewGroup类的addView()方法就很清楚了。

4)measure阶段。在measure阶段,我们会测量自己的大小,同时也要测量子View的大小,并且将子View的信息保存在LayoutParams中。

5)layout阶段。根据各个子View的信息,布局他们的位置。

最后加上布局文件。

<?xml version="1.0" encoding="utf-8"?>
<!--添加自定义属性给viewGroup-->
<!--新添加的命名空间的后缀必须保持和.xml中声明的包名一致-->
<com.app.CustomViewMotion.CascadeViewGroup
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:ts="http://schemas.android.com/apk/res/com.app.CustomViewMotion"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        ts:horizontalspacing="15dp"
        ts:verticalspacing="15dp">

    <TextView android:layout_width="100dp"
              android:layout_height="100dp"
              android:gravity="center"
              android:text="text1"
              android:background="#668B8B"/>

    <TextView android:layout_width="100dp"
              android:layout_height="100dp"
              android:gravity="center"
              android:text="text2"
              android:background="#FFDAB9"/>

    <TextView android:layout_width="100dp"
              android:layout_height="100dp"
              android:gravity="center"
              android:text="text3"
              android:background="#43CD80"/>

<!--这个子view中添加自定义子view属性-->
    <TextView android:layout_width="100dp"
              android:layout_height="100dp"
              android:gravity="center"
              android:text="text4"
              ts:layout_paddingleft="100dp"
              ts:layout_paddinTop="100dp"
              android:background="#00CED1"/>
</com.app.CustomViewMotion.CascadeViewGroup>

实现的效果如下:

时间: 2024-11-07 08:58:37

android中自定义ViewGroup的实现的相关文章

Android中自定义ViewGroup实现表格展示学员信息

前一段时间有个Android刚入门的朋友想实现一个表格 来展示信息,下面我们通过扩展ViewGroup 来实现一个简单的. 本文通过扩展Android ViewGroup实现表格 可用于课程信息,学生信息视图展示,实现表格方式可以用布局拼凑 也可以自定义ViewGroup方式实现. 最终效果如下: 首先创建基本模型和Activity public class Student { /** * */ public Student() { // TODO Auto-generated construc

Android中自定义下拉样式Spinner

Android中自定义下拉样式Spinner 本文继续介绍android自定义控件系列,自定义Spinner控件的使用. 实现思路 1.定义下拉控件布局(ListView及子控件布局) 2.自定义SpinerPopWindow类 3.定义填充数据的Adapter 效果图 一.定义控件布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http:/

android中自定义view涉及到的绘制知识

android中自定义view的过程中,需要了解的绘制知识. 1.画笔paint: 画笔设置: <span style="font-size:14px;"> paint.setAntiAlias(true);//抗锯齿功能 paint.setColor(Color.RED); //设置画笔颜色 paint.setStyle(Style.FILL);//设置填充样式 paint.setStrokeWidth(30);//设置画笔宽度 paint.setShadowLayer(

android中自定义下拉框(转)

android自带的下拉框好用不?我觉得有时候好用,有时候难有,项目规定这样的效果,自带的控件实现不了,那么只有我们自己来老老实实滴写一个新的了,其实最基本的下拉框就像一些资料填写时,点击的时候出现在编辑框的下面,然后又很多选项的下拉框,可是我在网上找了一下,没有这种下拉框额,就自己写了一个,看效果图先: ,这个是资料填写的一部分界面,三个下拉框,选择故乡所在地: 点击之后弹出下拉框,选择下面的选项: 三个下拉框时关联的,第一个决定了第二数据内容,第二个决定了第三个数据内容,如果三个全部选好之后

Android 中自定义软键盘

Android 中自定义软键盘    图一为搜狗输入法.图二为自定义密码键盘.图三为自定义密码键盘 java源文件 package com.keyboarddemo; import android.content.Context; import android.graphics.Paint; import android.graphics.Rect; import android.text.method.PasswordTransformationMethod; import android.u

Android中自定义View的MeasureSpec使用

有时,Android系统控件无法满足我们的需求,因此有必要自定义View.具体方法参见官方开发文档:http://developer.android.com/guide/topics/ui/custom-components.html 一般来说,自定义控件都会去重写View的onMeasure方法,因为该方法指定该控件在屏幕上的大小. protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) onMeasure传

Android中自定义ListView无法响应OnItemClickListener中的onItemClick方法问题解决方案

如果你的自定义ListViewItem中有Button或者Checkable的子类控件的话,那么默认focus是交给了子控件,而ListView 的Item能被选中的基础是它能获取Focus,也就是说我们可以通过将ListView中Item中包含的所有控件的focusable属性设置为 false,这样的话ListView的Item自动获得了Focus的权限,也就可以被选中了 我们可以通过对Item Layout的根控件设置其android:descendantFocusability="blo

Android中自定义视图View之---前奏篇

前言 好长时间没写blog了,心里感觉有点空荡荡的,今天有时间就来写一个关于自定义视图的的blog吧.关于这篇blog,网上已经有很多案例了,其实没什么难度的.但是我们在开发的过程中有时候会用到一些自定义的View以达到我们所需要的效果.其实网上的很多案例我们看完之后,发现这部分没什么难度的,我总结了两点: 1.准备纸和笔,计算坐标 2.在onDraw方法中开始画图,invalidate方法刷新,onTouchEvent方法监听触摸事件 对于绘图相关的知识,之前在弄JavaSE相关的知识的时候,

[转]Android中自定义样式与View的构造函数中的第三个参数defStyle的意义

转自:http://www.cnblogs.com/angeldevil/p/3479431.html Android中自定义样式与View的构造函数中的第三个参数defStyle的意义 零.序 一.自定义Style 二.在XML中为属性声明属性值 1. 在layout中定义属性 2. 设置Style 3. 通过Theme指定 三.在运行时获取属性值 1. View的第三个构造函数的第三个参数defStyle 2. obtailStyledAttributes 3. Example 四.结论与代