安卓开发 之小白养成-Android自定义View

  • 自定义View的作用和场景

Android中内置的UI控件和布局⽆无法满⾜足需求的时候,就需要进⾏外观、操作都是⾃自定义的⼀一些控件,  定义View。

 

  • 自定义View三种方式
  1. 继承已有的控件来实现自定义控件

主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。增加新的功能。  

2.继承ViewGroup,把多个控件组合成一个自定义控件(组合控件)。

以上两种方式均不满足的时候,考虑完全自定义控件。

3.通过View类来完全自定义控件。

  1. 继承已有控件实现自定义控件(案例:NotePad)

       需求:实现带下划线的文本输入框

       EditText  :  增加下滑线   

      定义类继承EditText  , 重写方法

     重点一:构造方法

  1. 一个参数的构造方法:在代码中使⽤用;类似: TextView txt = new TextView(context)。  一般必须有
  2. 两个或者三个参数的构造:通过 layout xml⽂文件包含控件的时候,会⾃自动调⽤用;例如:<com.qianfeng.NotePad></com.qianfeng.NotePad>  一般必须有
  3. 如果控件希望在layout使⽤用,控件中必须包含 两个或者三个参数的构造⽅方法,通常两个参数的构造⽅方法就⾏行了,没有强制要求三个构造⽅方法都写出来。

 分析View绘制的过程:

  1. 布局:谁使用谁布局
  2. 测量:测试控件宽高---自定义控件负责
  3. 绘制:----重写onDraw(Canvas  c)方法----该方法必须先调用super.onDraw();

         绘制要素:画布---Canvas对象

                        位置---draw方法中前四个参数

                         画笔-----Paint类

重点二:自定义View绘制

  1. 所有 View类都包含了一个 onDraw(Canvas c)方法
  2. onDraw这个方法的作⽤用,就是绘制View自身;
  3. View显⽰示成什么样⼦子需要 onDraw 把这个样⼦子画出来。
  4. 调用Canvas的各种drawXxxx()方法来绘制需要的样子。
  5. onDraw此方法系统会频繁的一直调用。尽量避免中此方法中创建任何的对象。

重点三:控件的坐标系统

重点四:Paint类

  1. Paint 主要⽤用于 Canvas 的各种绘制⽅方法中。
  2. 可以理解成是一个画笔。
  3. Paint 代表了绘制时,绘制的样式,例如颜⾊色、线宽、以及其他⽤用于绘制样式控制的属性;
  4. Canvas 几乎每⼀一个 drawXXXX 都会含有Paint参数

重点五:super.ondraw()

  1. 当继承已有控件时,注意 onDraw⽅方法内部需要调⽤用 super.onDraw 来绘制原有控件的内容。
  2. 因为⼦子类⾄至进⾏行特定内容的绘制,原有逻辑要保留

案例:带下划线的NotePad

实现步骤:

  1. 定义类继承edittext,必须重新构造方法

/**NotePadView  view = new NotePadView();

 * 这个构造方法必须有。

 * @param context

 */

 

public NotePadView(Context context) {

super(context);

init(context, null, 0);

}

/**

 * 这个构造方法也必须有是为了让别人xml布局文件中使用的

 * @param context 上下文对象

 * @param attrs  属性集

 */

public NotePadView(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs, 0);

}

/**

 * 不强制提供

 * @param context

 * @param attrs

 * @param type

 */

public NotePadView(Context context, AttributeSet attrs, int type){

super(context, attrs, type);

init(context, attrs, type);

}

//自定义的初始化方法,用于初始化画笔

private void init(Context context, AttributeSet attrs, int type) {

paint = new Paint();

//设置画笔的颜色,

paint.setColor(Color.RED);

//设置画笔的线条宽度

paint.setStrokeWidth(1);

}

  1. 重写onDraw方法

/*重写的onDraw方法,该方法由系统回调,用来绘制控件,我们在这里加入我们的新的绘制数据

 * 画画:

 * 1、笔

 * 2、画布

 * 参数:就是你要画的数据使用的画布

 * 注意:由于onDraw方法频繁调用,应尽量避免中里面创建对象,因此画笔的创建写在了构造方法中

 */

@Override

protected void onDraw(Canvas canvas) {

//父类的super.onDraw()必须调用

super.onDraw(canvas);

 

//EditText提供了获得每行高度的方法

int lineHeight = getLineHeight();

//EditText有个默认的Pading

int paddingTop = getPaddingTop();

//获得当前控件的宽度

int width = getWidth();

//计算当前屏幕的行数: 屏幕的高度 / 每行的高度

int rows = getHeight() / lineHeight;

//获得已经输入的行

int lineCount = getLineCount();

//输入行数和使用屏幕高度计算出来的行数,谁大用谁

int count = rows > lineCount ? rows : lineCount;

for(int i = 0; i < count; i++){

//Paint 画笔

canvas.drawLine(0, lineHeight * (i + 1) + paddingTop, width, lineHeight * (i + 1) + paddingTop, paint);

}

}

.可选步骤:向外界提供方法,改变线条的颜色和宽度

/**

 * 设置横线的颜色值

 */

public void setLineColor(int color){

paint.setColor(color);

//立马去更新视图. 使视图无效

invalidate();

}

/**

 * 设置横线的宽度

 * @param px

 */

public void setLineThick(int px){

paint.setStrokeWidth(px);

invalidate();

}

  1. 在xml文件中使用

<com.qf.day29mynote.NotePadView 

      android:id="@+id/notePadView"

      android:layout_width="match_parent"

      android:layout_height="match_parent" 

      android:gravity="top"    android:background="@drawable/circle_ret"/>

  1. 组合控件(案例:组合textview      瀑布流)
  1. 组合控件是自定义控件的一种,只不过它是由其他几个原生控件组合而成,故名组合控件。
  2. 在实际项目中,GUI会遇到一些可以提取出来做成自定义控件情况。一个自定义控件的好处就是把一些需要模块化的UI和逻辑放在一起,做到了高内聚,向其他模块提供接口并很少 依赖外界,这样就是低耦合。
  3. 一个自定义控件就是一个封闭的王国,这里由你掌控。就像逻辑部分的模块化一样。

例  瀑布流效果图:

自定义瀑布流控件,向外界提供添加图片的方法,

1.定义类继承,scrollView

2.在scrollView中添加  一个h线性布局  在其中 装三个 V 线性布局

3.向外提供添加图片的方法

 

实现思路:继承ScrollView //可滚动的视图

 

实现步骤:

  1. 定义类继承于ScrollView
  2. 重写构造方法,在构造方法中设置布局,其中设置一个竖直方向的线性布局嵌套3个水平方向的线性布局

private void init(Context context, AttributeSet attrs, int defStyle) {

this.context = context;

colums = new ArrayList<>();

setBackgroundColor(Color.RED);

 

LinearLayout layout = new LinearLayout(context);

layout.setBackgroundColor(Color.RED);

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(

LinearLayout.LayoutParams.MATCH_PARENT,

LinearLayout.LayoutParams.MATCH_PARENT);

layout.setLayoutParams(params);

layout.setOrientation(LinearLayout.HORIZONTAL);

this.addView(layout);

 

// 宽0 高 匹配父容器 比重1

LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(0,

LinearLayout.LayoutParams.MATCH_PARENT, 1);

for (int i = 0; i < 3; i++) {

LinearLayout layout1 = new LinearLayout(context);

layout1.setOrientation(LinearLayout.VERTICAL);

switch (i) {

case 0:

layout1.setBackgroundColor(Color.GREEN);

break;

case 1:

layout1.setBackgroundColor(Color.YELLOW);

break;

case 2:

layout1.setBackgroundColor(Color.CYAN);

break;

}

layout1.setLayoutParams(params1);

// 给外层的水平的线性布局添加控件

layout.addView(layout1);

colums.add(layout1);

}

}

  1. 向外提供设置图片的方法

public void addBitmap(Bitmap bitmap) {

ImageView imageView = new ImageView(context);

imageView.setImageBitmap(bitmap);

imageView.setScaleType(ScaleType.CENTER_CROP);

 

colums.get(bitmapCount % 3).addView(imageView);

bitmapCount++;

}

  1. 在activity中调用方法添加图片

MyImagesView mImagesView = (MyImagesView) findViewById(R.id.myImagesView);

AssetManager assets = getAssets();

InputStream inputStream = null;

try {

String[] list = assets.list("images");

 

for (int i = 0; i < list.length; i++) {

inputStream = assets.open("images/"+list[i]);

mImagesView.addBitmap(BitmapFactory.decodeStream(inputStream));

}

} catch (IOException e) {

e.printStackTrace();

}

 

  1. 完全自定义控件

 

  1. 三个构造方法至少实现两个。 (1个参数的和2个参数的)
  2. 覆写onMeasure。该方法比onDraw方法先执行。
  3. onDraw方法时绘制
  1. onMeasure方法

该方法用来测量控件的尺寸。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

参数:父控件传入的子控件能获得空间及关于这个空间描述的元数据。拿到这两个参数之后需要把他们给译解出来。然后和控件的宽和高,最后通过setMeasuredDimension(measuredHeight, measuredWidth)把控件的宽高保存起来。

 

  1. // 父容器传过来的宽度方向上的模式

 int widthMode = MeasureSpec.getMode(widthMeasureSpec);

// 父容器传过来的高度方向上的模式

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

注意:依据specMode的值,

MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST)

如果是AT_MOST,specSize 代表的是最大可获得的空间(wrap_content);

如果是EXACTLY,specSize 代表的是精确的尺寸(match_parent 、100dp等);

如果是UNSPECIFIED,父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小; 这种模式实际很难碰到

  1. MeasureSpec.getSize(widthMeasureSpec):

获取父容器传来的宽度

  1. setMeasuredDimension(widthSize, heightSize);

设置控件的宽高尺寸。该方法最后必须调用。

  1. onDraw方法

根据需要绘制自己需要的图形、字符等。

/获得描述字体的矩阵信息对象

FontMetrics fontMetrics = paint.getFontMetrics();

//获得中间到底部的宽度

fontMetrics.descent

  1. OnTouchEvent方法(事件处理)

对事件进行处理。

//获得事件类型的  2.2支持多点触摸之后建议使用掩码

switch (event.getAction() & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_DOWN:

 

break;

case MotionEvent.ACTION_MOVE:

break;

case MotionEvent.ACTION_UP:

 

break;

default:

break;

}

return true;// 该控件消耗掉该次触摸事件

}

  1. 实现自定义状态按钮
  1. 定义类,继承于view
  2. 重写构造方法,在其中做好一些初始化工作,例如准备好图片,获得图片宽高数据

private void init(Context context, AttributeSet attrs, int defStyleAttr) {

// TODO Auto-generated method stub

//得到背景图片的bitmap对象

slideBgOn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_bg_on);

slideBgOff = BitmapFactory.decodeResource(getResources(), R.drawable.slide_bg_off);

//得到滑块图片的bitmap对象

slideBtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_btn);

 

//得到背景图片的宽高

bgHeight = slideBgOff.getHeight();

bgWidth = slideBgOff.getWidth();

//得到滑块的宽度

btnWidth = slideBtn.getWidth();

}

3.重写onMeasure方法,该方法用于测量

暂时先写这一句,必须写

setMeasuredDimension(bgWidth, bgHeight);

4.重写onDraw方法

5.重写onTouchEvent方法:该方法在用户手指触摸该控件的时候回调

6.向外提供改变按钮状态的方法

7.设置改变按钮状态的监听器

 

在onTouchEvent方法中,合适的地方调用接口中的方法

8.设置自定义属性---参考2.3.6

9.重写测量方法

  1. 实现自定义消息数量图标
  1. 自定义类继承于view 重写构造方法
  2. 重写onDraw方法,在方法中绘制

  1. 自定义属性

使用资源文件声明自定义属性

以下代码必须放在res/values 目录下

 

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

<resources>

    <declare-styleable name="slide">

        <attr name="slideState" format="boolean" />

    </declare-styleable>

</resources>

布局文件自定控件中使用自定义属性

如果你用到了自定义属性,你都必须在activity布局文件的最开始这样声明:

 

 

 

 

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

<RelativeLayout

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

    xmlns:my="http://schemas.android.com/apk/res/应用程序包名"

<自定义控件

my:slideState="true"    使用命名空间my(随意写,但是两者要一致)

</自定义控件>

在自定的控件类中 取得自定义属性(一般写在构造方法中)

TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.slide);  //得到自定义的属性组成的数组

slideState = a.getBoolean(0, false);   //获取属性的值

附录:自定义属性

1. reference:参考某一资源ID。

(1)属性定义:

<declare-styleable name="名称">

    <attr format="reference" name="background" />

</declare-styleable>

2. color:颜色值。

(1)属性定义:

<declare-styleable name="名称">

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

</declare-styleable>

3. boolean:布尔值。

(1)属性定义:

<declare-styleable name="名称">

    <attr format="boolean" name="focusable" />

</declare-styleable>

4. dimension:尺寸值。

(1)属性定义:

<declare-styleable name="名称">

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

</declare-styleable>

5. float:浮点值。

(1)属性定义:

<declare-styleable name="AlphaAnimation">

    <attr format="float" name="fromAlpha" />

    <attr format="float" name="toAlpha" />

</declare-styleable>

6. integer:整型值。

(1)属性定义:

<declare-styleable name="AnimatedRotateDrawable">

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

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

</declare-styleable>

7. string:字符串。

(1)属性定义:

<declare-styleable name="MapView">

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

</declare-styleable>

8. fraction:百分数。

(1)属性定义:

 

<declare-styleable name="RotateDrawable">

    <attr format="fraction" name="pivotX" />

    <attr format="fraction" name="pivotY" />

</declare-styleable>

9. enum:枚举值。

(1)属性定义:

<declare-styleable name="名称">

    <attr name="orientation">

        <enum name="horizontal" value="0" />

        <enum name="vertical" value="1" />

    </attr>

</declare-styleable>

10. flag:位或运算。

(1)属性定义:

<declare-styleable name="名称">

    <attr name="windowSoftInputMode">

        <flag name="stateUnspecified" value="0" />

        <flag name="stateUnchanged" value="1" />

        <flag name="stateHidden" value="2" />

        <flag name="stateAlwaysHidden" value="3" />

        <flag name="stateVisible" value="4" />

        <flag name="stateAlwaysVisible" value="5" />

        <flag name="adjustUnspecified" value="0x00" />

        <flag name="adjustResize" value="0x10" />

        <flag name="adjustPan" value="0x20" />

        <flag name="adjustNothing" value="0x30" />

    </attr>

</declare-styleable>

(1)属性定义:

<declare-styleable name="名称">

    <attr format="reference|color" name="background" />

</declare-styleable>

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值