自定义View简单使用

  1.创建一个包:weight

  2.创建一个类:ClicleView  extends  View

  四个构造方法

          Context context  上下文引用

      AttributeSet attrs    自定义属性集

              int defStyleAttr  默认样式

      int defStyleRes  默认资源引用

  public SubView(Context context) {

          super(context);

      }

  调用自身构造函数

  public SubView(Context context) {

          this(context, null);

      }

 

      //AttributeSet attrs    自定义属性集

      //自定义属性从XML文件中进行获取

      public SubView(Context context, @Nullable AttributeSet attrs) {

          this(context, attrs, 0);

      }

 

      public SubView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

          super(context, attrs, defStyleAttr);

      }

   

    defStyleAttr Android默认样式

   

      xmlns:android="http://schemas.android.com/apk/res/android"  Android原生的命名空间集(删除,Android取值会取不到)

      xmlns:day="http://schemas.android.com/apk/res-auto"  Android命名空间

   

   

    自定义View:

  1、构造函数

  2、设置XML中的命名空间

  3、设置自定义属性

  1.创建自定义属性:(在res/values/attrs  创建一个属性集)

  2.声明自定义属性:

  <resources>

  <declare-styleable name="SubView">

  <attr name="myText" format="string"></attr>

  <attr name="myColor" format="color"></attr>

  <attr name="myTextSize" format="dimension"></attr>

  </declare-styleable>

  </resources>

  通过attr标签,声明属性值,并且规定接受类型

  3.自定义属性取值:

  ①从res/values/attrs.xml中去取属性

  ②需要对属性声明名称

  4.获取自定义属性集

  TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CircleView)

  遍历自定义属性集,获取自定义属性(for循环遍历)

  获取完毕后对自定义属性集中的下标内容进行获取

  typedArray.getIndex(i);

  4、对自定义控件进行设置

  1.onLayout 控件位置的摆放(根据控件组摆放)

  2.onMeasure  测量大小

  3.onDraw  绘制出所显示的内容

  onDraw(Canvas canvas){//cancas画板对象

  //创建画笔对象

  Paint paint = new Paint();

  paint.setColor(mColor);//设置画笔颜色

  canvas.drawCircle(0,0,mRadius,paint);//画圆

  //参数:1.X轴坐标 2.Y轴坐标 3.圆的半径 4.画笔

  }

 

    setMeasuredDimesion();//根据控件的具体宽高进行设置

    invalidate();//重绘方法

    paint.setStyle(Paint.Style.STROKE);//设置画笔描边样式(空心圆)

 

 

 

 

    自定义View绘制流程:

    首先我们自定义view需要继承自android.View,能帮你处理android命名空间的属性,比如说android:layout_width=”match_parent”……,当然如果我们的控件需要自定的控件属性,则需要在attrs.xml文件中预定义样式格式,如下:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <declare-styleable name="MyImageView">

            <attr name="src" format="integer"/>

    </declare-styleable>

</resources>

  1

 

然后在使用过程中我们需要在layout文件中声明命名空间:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    xmlns:img="http://schemas.android.com/apk/res-auto"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context="com.example.sosky.skytalk.FriendFragment">

    <RelativeLayout

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:layout_marginBottom="85dp">

    <!-- TODO: Update blank fragment layout -->

    <com.example.sosky.skytalk.MyImageView

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        img:src="@drawable/road"/>

    </RelativeLayout>

</FrameLayout>

 

这样声明命令空间xmlns:img=”http://schemas.android.com/apk/res-auto”,还可以xmlns:img=”http://schemas.android.com/apk/res/包名”声明命名空间。

接下来我们就需要测量大小和绘制view,下面是我自定义的一个图片控件:

package com.example.sosky.skytalk;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Bitmap;

import android.graphics.Canvas;

import android.graphics.Paint;

import android.graphics.drawable.BitmapDrawable;

import android.graphics.drawable.Drawable;

import android.icu.util.Measure;

import android.support.v4.content.ContextCompat;

import android.util.AttributeSet;

import android.view.View;

import java.util.jar.Attributes;

/**

* Created by sOSky on 2017/11/21.

*/

public class MyImageView extends View {

    private Paint mBitmapPaint;

    private Drawable mDrawable;

    private Bitmap mBitmap;

    private int mWidth;

    private int mHeight;

    public MyImageView(Context context){

        this(context, null);

    }

    public MyImageView(Context context, AttributeSet attributeSet){

        super(context,attributeSet);

        initAtters(attributeSet);

        mBitmapPaint = new Paint();

        mBitmapPaint.setAntiAlias(true);

    }

    private void initAtters(AttributeSet attributeSet){

        if (attributeSet != null) {

            TypedArray array = null;

            try {

                array = getContext().obtainStyledAttributes(attributeSet,R.styleable.MyImageView);

                mDrawable =array.getDrawable(R.styleable.MyImageView_src);

                measureDrawable();

            }finally {

                if (array != null){

                    array.recycle();

                }

            }

        }

    }

    private void measureDrawable(){

        if (mDrawable == null) {

            throw new RuntimeException("图片不存在!!!");

        }

        mWidth = mDrawable.getIntrinsicWidth();

        mHeight = mDrawable.getIntrinsicWidth();

    }

    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){

        int widthmod = MeasureSpec.getMode(widthMeasureSpec);

        int width = MeasureSpec.getSize(widthMeasureSpec);

        int heightmod = MeasureSpec.getMode(heightMeasureSpec);

        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(measureWidth(widthmod,width),measureHeight(heightmod,height));

    }

    @Override

    protected void onDraw(Canvas canvas){

        if (mBitmap == null){

            mBitmap = Bitmap.createScaledBitmap(ImageUtils.drawableToBitmap(mDrawable),getMeasuredWidth(),getMeasuredHeight(),true);

        }

        canvas.drawBitmap(mBitmap,getLeft(),getTop(),mBitmapPaint);

    }

    private int measureWidth(int mod, int width){

        switch(mod){

            case MeasureSpec.UNSPECIFIED:

            case MeasureSpec.AT_MOST:

                break;

            case MeasureSpec.EXACTLY:

                mWidth = width;

        }

        return mWidth;

    }

    private int measureHeight(int mod , int height){

        switch(mod){

            case MeasureSpec.UNSPECIFIED:

            case MeasureSpec.AT_MOST:

            break;

            case MeasureSpec.EXACTLY:

                mHeight = height;

        }

        return mHeight;

    }

}

  1

 

这里主要关注的两点是,获取开发人员设置属性值和根据父视图测量自身大小。

获取属性值主要是通过Context.obtainStyledAttributes(attribueSet attrs,int styleableid);来获取开发人员设定的属性集中我们需要的属性,在这里我们需要的就是R.styleable.MyImageView。然后对获得的资源进行处理。

测量自身的大小我们需要复写onMeasure(int measurewidth,int measureheight );这两个参数是整个android View绘制的关键。

Measurespec是一个32位整形数据,前两位是测量模式(specMod),后30位父视图的窗口大小(specSize)。

三个测量模式分别是

MeasureSpec.EXACTLY:父视图希望子视图大小应该是由父视图窗口大小来决定,也可以设置成任意的大小。match_parent,具体的数值都是这个模式。

MeasureSpec.AT_MOST:表示子视图只能是specSize中指定的大小,不能超过specSize的大小,一般来说warp_context对应这个模式。

MeasureSpec.UNSPECIFIED:表示子视图能够设定为任意值。使用情况较少,系统开发会用到。

这样我们的子视图就能获取到父视图的窗口大小,再通过开发人员设置的layout属性,就能计算出view的大小。

我当时看到这里产生了一个疑问,这个measurespecwidth和Measurespecheight是从哪里来的。查了资料后,明白了其实是由一个ViewRootImpl类创建的,它是整个view绘制流程的控制类,所有view都是通过它绘制出来的。

根视图的MeasureSpec是由窗口大小和自身LayoutParams决定的,一般来说都是EXACTLY模式和窗口大小,然后他会调用

performMeasure(int childwidthSpec, int childheightSpec){

…..

mView.measure(childwidthSpec,childheightSpec)

……

}

可以看到它是调用的viewGroup的测量函数,遍历当中所有的子view,方式就是调用每一个view的onMeasure()函数。这样子view就获得了测绘模式,父视图大小和自身设定的layoutParams,通过这三个信息,就能计算出具体的大小。

   

   

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容