Android中使用自定义ViewGroup的总结

分类

自定义Layout可以分为两种情况。

  • 自定义ViewGroup,创造出一些不同于LinearLayout,RelativeLayout等之类的ViewGroup。比如:API 14以后增加的GridLayout、design support library中的CoordinatorLayout等等。
  • 自定义一些已经有的Layout然后加一些特殊的功能。比如:TableLayout以及percent support library中的PercentFrameLayout等等。

流程

自定义View的流程是:onMeasure()->onLayout()->onDraw()。自定义ViewGroup的时候一般是不要去实现onDraw的,当然也可能有特殊的需求,比如:CoordinatorLayout。

所以onMeasure和onLayout基本能做大部分我们接触的ViewGroup。但是仅仅的知道在onMeasure中测量ViewGroup的大小以及在onLayout中计算Child View的位置还是不够。

比如:怎么可以给ViewGroup的Child View设置属性?

一个例子。

写一个自定义的ViewGroup,增加一个属性控制Child View的大小(长宽)占ViewGroup的比例。

假设是一个LinearLayout,那么就先定义一个CustomLinearLayout。

public class CustomLinearLayout extends LinearLayout {
  public CustomLinearLayout(Context context) {
    super(context);
  }

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

  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }
}

其它抛开不说,我们是需要增加属性用来控制Child View的大小。那么就先在value/attr.xml中定义那个属性(使用CustomLinearLayout_Layout来与CustomLinearLayout区分下,当然这个名字是随意的)。

<declare-styleable name="CustomLinearLayout_Layout">
  <!-- 定义比例 -->
  <attr name="inner_percent" format="float"/>
</declare-styleable>

ViewGroup调用addView()的时候最终都会调用到这个方法。

public void addView(View child, int index, LayoutParams params)

这个params代表的就是View的配置,ViewGroup.LayoutParams中就包含了width、height,LinearLayout.LayoutParams增加了weight属性等等。那么我们就应该实现一个LayoutParams。那么现在就是这样了。

public class CustomLinearLayout extends LinearLayout {
  public CustomLinearLayout(Context context) {
    super(context);
  }

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

  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }
  public static class LayoutParams extends LinearLayout.LayoutParams {

    private float innerPercent;

    private static final int DEFAULT_WIDTH = WRAP_CONTENT;
    private static final int DEFAULT_HEIGHT = WRAP_CONTENT;

    public LayoutParams() {
      super(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      innerPercent = -1.0f;
    }

    public LayoutParams(float innerPercent) {
      super(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      this.innerPercent = innerPercent;
    }

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

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public LayoutParams(LinearLayout.LayoutParams source) {
      super(source);
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public LayoutParams(LayoutParams source) {
      super(source);
      this.innerPercent = source.innerPercent;
    }

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

    private void init(Context context, AttributeSet attrs) {
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomLinearLayout_Layout);
      innerPercent = a.getFloat(R.styleable.CustomLinearLayout_Layout_inner_percent, -1.0f);
      a.recycle();
    }
  }
}

现在就可以在xml使用我们的属性了。

<?xml version="1.0" encoding="utf-8"?>
<com.egos.samples.custom_layout.CustomLinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="200dp"
  android:layout_height="200dp"
  android:id="@+id/test_layout"
  android:background="#ffff0000"
  android:gravity="center"
  android:orientation="vertical">
  <ImageView
    android:text="Egos"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:onClick="add"
    android:background="#ff00ff00"
    app:inner_percent="0.8"/>
</com.egos.samples.custom_layout.CustomLinearLayout>

只是然并软,并没有作用。

那么到底是怎么去控制Child View的大小呢?当然是在onMeasure控制的。addView会执行下面的代码。

requestLayout();
invalidate(true);

这样的话就会重新的走一遍onMeasure(),onLayout()了。实现onMeasure()的方法以后直接去处理Child View的大小,因为我继承的是LinearLayout,所以其实是会处理到measureChildBeforeLayout()。最终是在measureChildBeforeLayout的时候来处理Child View的大小。

@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
                    int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
  // 在xml强制写成match_parent,然后在这里强制设置成
  if (child != null && child.getLayoutParams() instanceof LayoutParams &&
      ((LayoutParams) child.getLayoutParams()).innerPercent != -1.0f) {
    parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (MeasureSpec.getSize(parentWidthMeasureSpec) *
        ((LayoutParams) child.getLayoutParams()).innerPercent), MeasureSpec.getMode(parentWidthMeasureSpec));
    parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (MeasureSpec.getSize(parentHeightMeasureSpec) *
        ((LayoutParams) child.getLayoutParams()).innerPercent), MeasureSpec.getMode(parentHeightMeasureSpec));
    super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
      parentHeightMeasureSpec, heightUsed);
  } else {
    super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
        parentHeightMeasureSpec, heightUsed);
  }
}

这样就可以实现最开始的需求了。

其实还有一些细节是需要处理的,下面的代码就是。

/**
 * 当checkLayoutParams返回false的时候就会执行到这里的generateLayoutParams
 */
 @Override
protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
  return super.generateLayoutParams(lp);
}

/**
 * 当addView的时候没有设置LayoutParams的话就会默认执行这里的generateDefaultLayoutParams
 */
@Override
protected LayoutParams generateDefaultLayoutParams() {
  return new LayoutParams();
}

/**
 * 写在xml中属性的时候就会执行这里的generateLayoutParams
 */
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new LayoutParams(getContext(), attrs);
}

总结一下

  • 自定义View和ViewGroup需要多多注意的都是onMeasure、onLayout、onDraw。
  • 把ViewGroup自身的属性和Child View的属性区分开。
  • 可以多多的参考support包中的代码,调试也非常的方便。

做Android开发,自身需要自定义View的地方确实是比较的多,只是大部分都会有相应的开源库。但是我们还是应该需要熟练的知道该如何自定义一个ViewGroup。

自己是一个比较健忘的人,所以写的详细点。

完整的代码戳这里。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android ViewDragHelper完全解析 自定义ViewGroup神器

    一.概述 在自定义ViewGroup中,很多效果都包含用户手指去拖动其内部的某个View(eg:侧滑菜单等),针对具体的需要去写好onInterceptTouchEvent和onTouchEvent这两个方法是一件很不容易的事,需要自己去处理:多手指的处理.加速度检测等等. 好在官方在v4的支持包中提供了ViewDragHelper这样一个类来帮助我们方便的编写自定义ViewGroup.简单看一下它的注释: ViewDragHelper is a utility class for writin

  • Android 重写ViewGroup 分析onMeasure()和onLayout()方法

    Android 重写ViewGroup 分析onMeasure()和onLayout()方法 在继承ViewGroup类时,需要重写两个方法,分别是onMeasure和onLayout. 1,在方法onMeasure中调用setMeasuredDimension方法 void android.view.View.setMeasuredDimension(int measuredWidth, int measuredHeight) 在onMeasure(int, int)中,必须调用setMeas

  • Android控件PullRefreshViewGroup实现下拉刷新和上拉加载

    本文实例为大家分享了Android实现下拉刷新和上拉加载更多的具体代码,供大家参考,具体内容如下 先分享下源码:Android实现下拉刷新和上拉加载更多 实现思路:由PullRefreshViewGroup控件来接管标准控件(比如RecyclerView.ListView等)的滑动,调用标准控件的内部方法进行短距离滑动,不再由标准控件自己来处理事件,而完全由PullRefreshViewGroup控件来处理触摸事件.标准控件内部的滑动距离等属性,通过反射获得computeVerticalScro

  • Android自定义控件之继承ViewGroup创建新容器

    欢迎大家来学习本节内容,前几节我们已经学习了其他几种自定义控件,分别是Andriod 自定义控件之音频条及 Andriod 自定义控件之创建可以复用的组合控件还没有学习的同学请先去学习下,因为本节将使用到上几节所讲述的内容. 在学习新内容之前,我们先来弄清楚两个问题: 1 . 什么是ViewGroup? ViewGroup是一种容器.它包含零个或以上的View及子View. 2 . ViewGroup有什么作用? ViewGroup内部可以用来存放多个View控件,并且根据自身的测量模式,来测量

  • Android自定义ViewGroup(侧滑菜单)详解及简单实例

    自定义侧滑菜单的简单实现 不少APP中都有这种侧滑菜单,例如QQ这类的,比较有名开源库如slidingmenu. 有兴趣的可以去研究研究这个开源库. 这里我们将一种自己的实现方法,把学习的 东西做个记录,O(∩_∩)O! 首先看效果图: 这里我们实现的侧滑菜单,是将左侧隐藏的菜单和主面板看作一个整体来实现的,而左侧隐藏的菜单和主面板相当于是这个自定义View的子View. 首先来构造该自定义View的布局: 自定义的SlideMenuView包含两个子view,一个是menuView,另一个是m

  • Android自定义ViewGroup实现标签流容器FlowLayout

    本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout,但不影响我对其的学习.和往常一样,主要还是想总结一下自定义ViewGroup的开发过程以及一些需要注意的地方. 按照惯例,我们先来看看效果图 一.写代码之前,有几个是问题是我们先要弄清楚的: 1.什么是ViewGroup:从名字上来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许

  • Android中使用自定义ViewGroup的总结

    分类 自定义Layout可以分为两种情况. 自定义ViewGroup,创造出一些不同于LinearLayout,RelativeLayout等之类的ViewGroup.比如:API 14以后增加的GridLayout.design support library中的CoordinatorLayout等等. 自定义一些已经有的Layout然后加一些特殊的功能.比如:TableLayout以及percent support library中的PercentFrameLayout等等. 流程 自定义V

  • Android 中TabLayout自定义选择背景滑块的实例代码

    TabLayout是Android 的Material Design包中的一个控件,可以和V4包中的ViewPager搭配产生一个联动的效果.这里我自定义了一个滑块能够跟随TabLayout进行滑动选择的SliderLayout.效果见下图(白色方框): 下面是SliderLayout的源码: import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawabl

  • Android中DialogFragment自定义背景与宽高的方法

    介绍 DialogFragment在android 3.0时被引入.是一种特殊的Fragment,用于在Activity的内容之上展示一个模态的对话框.典型的用于:展示警告框,输入框,确认框等等. 在DialogFragment产生之前,我们创建对话框:一般采用AlertDialog和Dialog.注:官方不推荐直接使用Dialog创建对话框. 本文主要给大家介绍了关于Android中DialogFragment自定义背景与宽高的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的

  • Android 中okhttp自定义Interceptor(缓存拦截器)

    Android 中okhttp自定义Interceptor(缓存拦截器) 前言: 新公司项目是没有缓存的,我的天,坑用户流量不是么.不知道有人就喜欢一个界面没事点来点去的么.怎么办?一个字"加". 由于项目的网络请求被我换成了retrofit.而retrofit的网络请求默认基于okhttp okhttp的缓存由返回的header 来决定.如果服务器支持缓存的话返回的headers里面会有这一句 "Cache-Control","max-age=time&

  • 解析Android中使用自定义字体的实现方法

    1.Android系统默认支持三种字体,分别为:"sans", "serif", "monospace 2.在Android中可以引入其他字体 . 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><TableLayout xmlns:Android="http://schemas.android.com/apk/res/android&qu

  • Android中制作自定义dialog对话框的实例分享

    自定义dialog基础版 很多时候,我们在使用android sdk提供的alerdialog的时候,会因为你的系统的不同而产生不同的效果,就好比如你刷的是MIUI的系统,弹出框都会在顶部显示!这里简单的介绍自定义弹出框的应用. 首先创建布局文件dialog: 代码: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.and

  • Android中View自定义组合控件的基本编写方法

    有很多情况下,我们只要运用好Android给我提供好的控件,经过布局巧妙的结合在一起,就是一个新的控件,我称之为"自定义组合控件". 那么,这种自定义组合控件在什么情况下用呢?或者大家在做项目时候会发现,某些布局会被重复的利用,同一个布局的XML代码块会被重复的复制黏贴多次,这样会造成代码结构混乱不说,代码量也会增大,各种控件都需要在Java代码中被申明和处理相应的逻辑,工作量着实不小,所以,必须要找到一个合理的"偷懒"的方式,开动脑经去怎么简化以上说的不必要的麻烦

  • Android中BaseActivity自定义标题栏

    再做一个项目的时候,要求标题栏的标题再中间,样式,字体大小都要自定义.左边一个返回按钮,一个关闭按钮,右边定义一个提交按钮,有时候显示有时候隐藏.因为原生的title标题是再左边的,然后去给Titlebar设置自定义View的时候,也会不尽人意,标题不是再正中间的,标题栏太高等问题. 我们要求的是这样的,右边的按钮可以显示或者隐藏. 于是就决定自己写一个BaseActivity,所有的都去继承这个基类,然后自己去定义标题栏的样式就可以就可以了. 下面来讲一下这个界面是怎么实现的: 首先定义一个类

  • android中RecyclerView自定义分割线实现

    最近一直在看RecyclerView,较之ListView它确实是灵活多变,给予开发者更多自定义的空间,比如:需要添加头部和尾部.item的点击事件.自定义的LayoutManager,还有就是下面要说的自定义的分割线. 1.如何理解分割线 经常听到有人说自定义分割线麻烦,为什么不把分割线写到item布局里,这样不是更简单吗?有些情况把分割线写到item布局里是很难达到我们想要的效果,例如RecyclerView里的GridLayoutManager,StaggeredGridLayoutMan

  • Android中MPAndroidChart自定义绘制最高点标识的方法

    前言 MPAndroidChart是一款基于Android的开源图表库,MPAndroidChart不仅可以在Android设备上绘制各种统计图表,而且可以对图表进行拖动和缩放操作,应用起来非常灵活.MPAndroidChart显得更为轻巧和简单,拥有常用的图表类型:线型图.饼图.柱状图和散点图. MPAndroidChart自定义绘制最高点标识 距离上次发布关于 MPAndroidChart 的文章已经过去一个多月了,项目中新增了一个需求,看起来很简单.就是在最高点绘制矩形框,标识最高点的数值

随机推荐