您的位置:首页 > 其它

自定义控件-CascadeLayout

2016-04-14 12:26 295 查看

(1)前言

android的进阶之路上,总少不了使用自定义控件。自定义控件按照不同的分法,有不同的分类,这里主要分为四类:

1 继承自view,重写 onDraw方法;比如系统的TextView,ImageView

2 继承自ViewGroup,实现自己的自定义控件;

3 继承自特定的view(比如ImageView),

圆角图片CircleImageView自带清除按钮的EditText

4 继承自特定的ViewGroup,(比如LinearLayout,ListView)自定义控件-下拉刷新和上拉加载的listView

view的工作流程是measure,layout和draw三大流程,也就是测量,布局和绘制,通过这三大步骤来完成这个view的布局以及显示。大多数时候我们都会选择后两种实现自定义控件,因为我们的一些系统控件已经帮我们处理好了各种测绘流程。今天来实现第2种。继承自ViewGroup,实现自己的自定义控件来实现如图所示的效果。很简单的自定义控件,但是对于了解和使用自定义控件有很大的帮助。



(2) 界面布局:

首先了解怎么使用,有个整体概念。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:cascade="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_height="match_parent">

<com.nsu.edu.cascadelayout.CascadeLayout

android:layout_width="fill_parent"
android:layout_height="fill_parent"
cascade:horizontal_spacing="30dp"
cascade:vertical_spacing="20dp">

<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#ff0000" />

<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#00ff00" />

<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#0000ff" />
</com.nsu.edu.cascadelayout.CascadeLayout>

</FrameLayout>


3 在attrs 定义自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CascadeLayout">
<attr name="horizontal_spacing" format="dimension"/>
<attr name="vertical_spacing" format="dimension"/>
</declare-styleable>
</resources>


注意在androidStudio中我们使用自定义属性的时候命名空间为:xmlns:cascade=”http://schemas.android.com/apk/res-auto”

4 在dimens.xml中添加自定义属性的默认值

<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="cascade_horizontal_spacing">10dp</dimen>
<dimen name="cascade_vertical_spacing">10dp</dimen>
</resources>


5 自定义控件布局继承自ViewGroup

package com.nsu.edu.cascadelayout;

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

/**
* Created by Anthony on 2016/1/28.
* Class Note:自定义控件实现叠加效果
*/
public class CascadeLayout extends ViewGroup {
private int horizontalSpacing;
private int verticalSpacing;

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

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

/**
*step1 从自定义属性中获取,如果其值没有指定,则使用默认值
*/
public CascadeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeLayout);
horizontalSpacing = a.getDimensionPixelSize(R.styleable.CascadeLayout_horizontal_spacing
, getResources().getDimensionPixelSize(R.dimen.cascade_horizontal_spacing));
verticalSpacing = a.getDimensionPixelSize(R.styleable.CascadeLayout_vertical_spacing,
getResources().getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
a.recycle();
}

/**
* step2 自定义LayoutParams ,该类用于保存每个子视图的x,y轴位置
*/
public class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;

public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}

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

public LayoutParams(ViewGroup.LayoutParams source, int x, int y) {
super(source);
this.x = x;
this.y = y;
}

}

/**
* step3 使用自定义LayoutParams必须重写下面的四个方法
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}

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

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

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

/**
* step4 重写onMeasure
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//使用宽和高计算布局的最终大小以及子视图的x和y轴位置
int width = 0;
int height = getPaddingTop();
//获取每个子视图
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
//让每个子视图测量自身
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//获取每个子视图的LayoutParams
LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = getPaddingLeft() + horizontalSpacing * i;
lp.x = width;
lp.y = height;//将宽和高保存到自定义的LayoutParams中去

width += child.getMeasuredWidth();
height += verticalSpacing;
}
//使用计算所得的宽和高设置整个布局的测量尺寸
width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight() + getPaddingBottom();
// resolveSize的主要作用就是根据你提供的大小和MeasureSpec,
// 返回你想要的大小值,这个里面根据传入模式的不同来做相应的处理
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec));
}

/**
*step 5 重写onLayout
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
}
}
}


步骤:

step1 构造函数中,从自定义属性中获取,如果其值没有指定,则使用默认值

step2 自定义LayoutParams ,该类用于保存每个子视图的x,y轴位置

step3 使用自定义LayoutParams必须重写下面的四个方法

step4 重写onMeasure

step 5 重写onLayout

继承ViewGroup不需要重写onDraw

本项目github地址:https://github.com/CameloeAnthony/CascadeLayout
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: