zoukankan      html  css  js  c++  java
  • 安卓进阶之自定义View

    安卓进阶之自定义View

    自定义View,可以分为具体的三大类:

    • 自定义View(继承系统控件/继承View)
    • 自定义Viewgroup(继承系统特定的Viewgroup/继承ViewGround)
    • 自定义组合控件

    ## 自定义View的工作流程和内容 ### 工作流程

    无论是哪一类View,只要是View,都需要经过以下工作流程:

    测量->布局->绘制.
    measure->layout->draw.

    1. measure阶段测量View的宽高
    2. layout阶段用来确定View的位置
    3. draw阶段则是用来绘制View.

    测量阶段和布局阶段的工作内容

    测量阶段(measure):从上到下递归地调用每个 View 或者 ViewGroup 的 measure() 方法,测量他们的尺寸并计算它们的位置;

    布局阶段(layout):从上到下递归地调用每个 View 或者 ViewGroup 的 layout() 方法,把测得的它们的尺寸和位置赋值给它们。

    View 和 ViewGroup 在测量阶段和布局阶段的区别

    1. 测量阶段,measure() 方法被父 View 调用,在 measure() 中做一些准备和优化工作后,调用 onMeasure() 来进行实际的自我测量。 onMeasure() 做的事,ViewViewGroup 不一样:
      1. ViewViewonMeasure() 中会计算出自己的尺寸然后保存;
      2. ViewGroupViewGrouponMeasure() 中会调用所有子 View 的 measure() 让它们进行自我测量,并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置(实际上 99.99% 的父 View 都会使用子 View 给出的期望尺寸来作为实际尺寸,原因在下期或下下期会讲到)然后保存。同时,它也会根据子 View 的尺寸和位置来计算出自己的尺寸然后保存;
    2. 布局阶段,layout() 方法被父 View 调用,在 layout() 中它会保存父 View 传进来的自己的位置和尺寸,并且调用 onLayout() 来进行实际的内部布局。onLayout() 做的事, ViewViewGroup 也不一样:
      1. View:由于没有子 View,所以 ViewonLayout() 什么也不做。
      2. ViewGroup:ViewGroup 在 onLayout() 中会调用自己的所有子 View 的 layout() 方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。

    绘制阶段的工作内容

    在官方注释中,绘制分为以下步骤:

    1. 如果需要,就绘制背景--drawBackgrounp()
    2. 保存当前canvas层
    3. 绘制View的内容--onDraw()
    4. 绘制子View--dispatchView()
    5. 如果需要,就绘制View的褪色边缘,类似于阴影效果
    6. 绘制装饰,比如滚动条--onDrawForeground()

    其中第2,5步可以跳过.具体如何绘制内容将在文末补充.


    ## 上手:实现继承View的自定义View

    我们通过继承View实现一个自定义View,往往需要实现以下内容:

    • 绘制内容(draw)

    • 对Padding进行处理(draw)

    • 对wrap_content进行处理(measure)

    • 创建自定义属性,配置自己的自定义View

    • 重写onTounchEvent()改变触摸反馈

      括号为涉及到的工作流程.

      例:

      在界面中,创建一个可以滑动的矩形

      java代码:

      public class CustomView extends View {
          Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
          int lastx;
          int lasty;
          int mColor;
          @Override
          public boolean onTouchEvent(MotionEvent event) {
              int x= (int) event.getX();
              int y= (int) event.getY();
      
              switch (event.getAction()) {
                  case MotionEvent.ACTION_DOWN:
                      lastx=x;
                      lasty=y;
                      break;
                  case MotionEvent.ACTION_MOVE:
                      int offsetX=x-lastx;
                      int offsetY=y-lasty;
                      ((View)getParent()).scrollBy(-offsetX,-offsetY);
                      break;
                  case MotionEvent.ACTION_UP:
                      break;
              }
              return true;
          }
      
          public CustomView(Context context) {
              super(context);
          }
      
          public CustomView(Context context, AttributeSet attrs) {
              super(context, attrs);
              //提取CustomView属性集合的rect_colot属性,如果不设置,默认为红色.
              TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.CustomView);
              mColor=typedArray.getColor(R.styleable.CustomView_rect_color, Color.RED);
              typedArray.recycle();
              paint.setColor(mColor);
              paint.setStrokeWidth((float)1.5);
          }
      
          public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
          }
      
          public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
              super(context, attrs, defStyleAttr, defStyleRes);
          }
      
          /*
          widthMeasureSpec和heightMeasureSpec分别压缩了mode和size两个信息.
          mode的分类: 
      	UNSPECIFIED:不限制,相当于match_parent
      	AT_MOST:限制上限,相当于wrap_content
      	EXACTLY:限制固定值
          */
          @Override
          protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
              super.onMeasure(widthMeasureSpec, heightMeasureSpec);
              //重新 onMeasure(),并计算出 View 的尺寸;
      		//(可以使用 resolveSize() 来让子 View 的计算结果符合父 View 的限制,也可以用自己的方式来满足父 View 的限制也行),		  本例子使用自己方式满足父 View 的限制。
              //对wrap_content进行处理
              //在onMeasure方法中指定一个默认的宽高,在设置wrap_content属性时设置此默认宽高
              int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
              int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
              int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
              int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
              if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
                  setMeasuredDimension(80,80);
              }else if(widthSpecMode==MeasureSpec.AT_MOST){
                  setMeasuredDimension(80,heightSpecSize);
              }else if(heightSpecMode==MeasureSpec.AT_MOST){
                  setMeasuredDimension(widthSpecSize,80);
              }
              /*resolveSize(int size, int widthMeasureSpec)
              方法内部的实现方式与例子自定义实现方式相似,从widthMeasureSpec得到
              mode的类别作判断
              UNSPECIFIED:不限制,返回size
      		AT_MOST:限制上限,size>MeasureSpec.getSize(widthMeasureSpec),返回后者,否则返回直接size
      		EXACTLY:限制固定值,返回MeasureSpec.getSize(widthMeasureSpec)
              */
              
             /*setMeasuredDimension(
             resolveSize(int size,int widthMeasureSpec),
             resolveSize(int size,int heightMeasureSpec)
             ); 
      		*/    
          }
      
          @Override
          protected void onDraw(Canvas canvas) {
              super.onDraw(canvas);
              //对padding进行处理
              int paddingLeft=getPaddingLeft();
              int paddingRight=getPaddingRight();
              int paddingTop=getPaddingTop();
              int paddingBottom=getPaddingBottom();
              int width=getWidth()-paddingLeft-paddingRight;
              int height=getHeight()-paddingTop-paddingBottom;
              //根据padding绘制矩形
              canvas.drawRect(0+paddingLeft,0+paddingTop,width+paddingLeft,height+paddingTop,paint);
          }
      
      }
      
      

      xml文件:

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".MainActivity">
      
          <com.example.drawtest.CustomView
              app:rect_color="@color/colorAccent"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              />
      
      </LinearLayout>
      

      以android开头的都是系统自带的属性,自定义属性需要在values目录下创建attrs.xml:

      <?xml version="1.0" encoding="utf-8"?>
      <resources>
          <declare-styleable name="CustomView">
              <attr name="rect_color" format="color"/>
          </declare-styleable>
      </resources>
      

    上手:自定义ViewGroup

    自定义ViewGroup的步骤其实就是对View工作流程中的测量阶段,布局阶段对应方法进行重写:

    1. 重写onMesure()
    2. 重写onLayout()

    重写 onMeasure() 的步骤:

    1.遍历子View,根据子View的LayoutParams属性以及ViewGroup的MeasureSpec模式得到子 View 的 MeasureSpec

    (子View的LayoutParams就是开发者对子View宽高等与位置相关属性的要求,而ViewGroup的mode则是开发者对ViewGroup宽高属性的要求.更简单的说,子View的LayoutParams保存了子View的xml代码中的Layout_height,Layout_width等有关的位置信息属性,ViewGroup的mode保存了ViewGroup的xml代码中的Layout_height,Layout_width属性).

    2.把计算出的子View的childWidthSpec和childHeightSpec作为参数传入子 View 的 measure()方法 来计算子 View 的尺寸

    3.子View在onMeasure()中计算自己最终的位置和尺寸利用setMeasuredDimension()方法保存

    4.ViewGroup通过子View的位置和尺寸确定自己的尺寸并用 setMeasuredDimension() 保存

    重写 onLayout() 的方式

    在 onLayout() 里调用每个子 View 的 layout() ,让它们保存自己的位置和尺寸。

    补充: 绘制内容的关键点

    • 自定义绘制的方式最常用的方式是重写onDraw()绘制方法

    • 绘制的关键是 Canvas 的使用

      • Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
      • Canvas 的辅助类方法:范围裁切和几何变换

      Paint:

      Paint 的 API 大致可以分为 4 类:颜色,效果,drawText() 相关,初始化.

      颜色类的API作用包括:直接设置颜色的 API 用来给图形和文字设置颜色(纯色,渐变色);加滤镜; 用来处理源图像和 View 已有内容的关系。

      效果类的 API 可以实现抗锯齿、填充/轮廓、线条宽度、线头形状,线拐角,线性过滤(使图像过渡平缓)等等。

      drawText()与初始化使用较少不作介绍.

      Canvas范围裁剪和几何变换:

      范围裁剪可以得到对原图像进行裁剪得到各种形状的图像,比如输入方形图片,输出圆形头像.

      几何变换可以实现平移,旋转,缩放,错切效果;

    • 可以使用不同的绘制方法来控制遮盖关系

    参考学习网站:HenCoderhttps://hencoder.com/

    参考学习书籍:Android进阶之光-刘望舒

  • 相关阅读:
    Git远程仓库的使用(github为例)
    SQL查看数据库中每张表的数据量和总数据量
    HTML简单的注册页面搭建
    java新建日期获取工具
    ArrayList的使用方法技巧(转载)
    UI初级 TextField
    UI初级 Label
    UI入门 纯代码 第一节 UIWindow, UIView
    C 入门 第十一节
    secoclient安装mac版提示系统配置文件写入失败的解决方案
  • 原文地址:https://www.cnblogs.com/adressian/p/10875082.html
Copyright © 2011-2022 走看看