自定义控件-CascadeLayout

来源:互联网 发布:常见的网络进攻形式 编辑:程序博客网 时间:2024/06/11 23:39

(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

0 0
原创粉丝点击