您的位置:首页 > 移动开发 > Android开发

Android自定义View的使用

2016-03-24 09:18 381 查看
原文地址:http://blog.csdn.net/chen_lian_/article/details/50939902

自定义View一直是横在Android开发者面前的一道坎。

一、View和ViewGroup的关系

从View和ViewGroup的关系来看,ViewGroup继承View。

View的子类,多是功能型的控件,提供绘制的样式,比如imageView,TextView等,而ViewGroup的子类,多用于管理控件的大小,位置,如LinearLayout,RelativeLayout等,从下图可以看出



从实际应用中看,他们又是组合关系,我们在布局中,常常是一个ViewGroup嵌套多个ViewGroup或View,而被嵌套的ViewGroup又会嵌套多个ViewGroup或View

如下



二、View的绘制流程

从View源码来看,主要关系三个方法:

1、measure():测量

一个final方法,控制控件的大小

2、layout():布局

用来控制自己的布局位置

有相对性,只相对于自己的父类布局,不关心祖宗布局

3、draw():绘制

用来控制控件的显示样式

流程: 流程 measure --> layout --> draw

对应于我们要实现的方法是

onMeasure()

onLayout()

onDraw()

实际绘制中,我们的思考顺序一般是这样的:

是否需要控制控件的大小-->是-->onMeasure()

(1)如果这个自定义view不是ViewGroup,onMeasure()方法调用setMeasureDeminsion(width,height):用来设置自己的大小

(2)如果是ViewGroup,onMeasure()方法调用 ,child.measure()测量孩子的大小,给出孩子的期望大小值,之后-->setMeasureDeminsion(width,height):用来设置自己的大小

是否需要控制控件的摆放位置-->是 -->onLayout ()

是否需要控制控件的样子-->是 -->onDraw ()-->canvas的绘制

下面是我绘制的流程图:



下面以自定义滑动按钮为例,说明自定义View的绘制流程

我们期待实现这样的效果:



拖动或点击按钮,开关向右滑动,变成



其中开关能随着手指的触摸滑动到相应位置,直到最后才固定在开关位置上

新建一个类继承自View,实现其两个构造方法

[java] view
plain copy







public class SwitchButtonView extends View {

public SwitchButtonView(Context context) {

this(context, null);

}

public SwitchButtonView(Context context, AttributeSet attrs) {

super(context, attrs);

}

drawable资源中添加这两张图片





借此,我们可以用onMeasure()确定这个控件的大小

[java] view
plain copy







@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);

mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);

setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());

}

这个控件并不需要控制其摆放位置,略过onLayout();

接下来onDraw()确定其形状。

但我们需要根据我们点击控件的不同行为来确定形状,这需要重写onTouchEvent()

其中的逻辑是:

当按下时,触发事件MotionEvent.Action_Down,若此时状态为关:

(1)若手指触摸点(通过event.getX()得到)在按钮的 中线右侧,按钮向右滑动一段距离(event.getX()与开关控件一半宽度之差,具体看下文源码)

(2)若手指触摸点在按钮中线左侧,按钮依旧处于最左(即“关”的状态)。

若此时状态为开:

(1)若手指触摸点在按钮中线左侧,按钮向左滑动一段距离

(2)若手指触摸点在按钮中线右侧,按钮依旧处于最右(即“开”的状态)

当滑动时,触发时间MotionEvent.Action_MOVE,逻辑与按下时一致

注意,onTouchEvent()需要设置返回值 为 Return true,否则无法响应滑动事件

当手指收起时,若开关中线位于整个控件中线左侧,设置状态为关,反之,设置为开。

具体源码如下所示:(还对外提供了一个暴露此时开关状态的接口)

自定义View部分

[java] view
plain copy







package com.lian.switchtogglebutton;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;

import android.graphics.Paint;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

/**

* Created by lian on 2016/3/20.

*/

public class SwitchButtonView extends View {

private static final int STATE_NULL = 0;//默认状态

private static final int STATE_DOWN = 1;

private static final int STATE_MOVE = 2;

private static final int STATE_UP = 3;

private Bitmap mSlideButton;

private Bitmap mSwitchButton;

private Paint mPaint = new Paint();

private int buttonState = STATE_NULL;

private float mDistance;

private boolean isOpened = false;

private onSwitchListener mListener;

public SwitchButtonView(Context context) {

this(context, null);

}

public SwitchButtonView(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);

mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);

setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (mSwitchButton!= null){

canvas.drawBitmap(mSwitchButton, 0, 0, mPaint);

}

//buttonState的值在onTouchEvent()中确定

switch (buttonState){

case STATE_DOWN:

case STATE_MOVE:

if (!isOpened){

float middle = mSlideButton.getWidth() / 2f;

if (mDistance > middle) {

float max = mSwitchButton.getWidth() - mSlideButton.getWidth();

float left = mDistance - middle;

if (left >= max) {

left = max;

}

canvas.drawBitmap(mSlideButton,left,0,mPaint);

}

else {

canvas.drawBitmap(mSlideButton,0,0,mPaint);

}

}else{

float middle = mSwitchButton.getWidth() - mSlideButton.getWidth() / 2f;

if (mDistance < middle){

float left = mDistance-mSlideButton.getWidth()/2f;

float min = 0;

if (left < 0){

left = min;

}

canvas.drawBitmap(mSlideButton,left,0,mPaint);

}else{

canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);

}

}

break;

case STATE_NULL:

case STATE_UP:

if (isOpened){

Log.d("开关","开着的");

canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);

}else{

Log.d("开关","关着的");

canvas.drawBitmap(mSlideButton,0,0,mPaint);

}

break;

default:

break;

}

}

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

mDistance = event.getX();

Log.d("DOWN","按下");

buttonState = STATE_DOWN;

invalidate();

break;

case MotionEvent.ACTION_MOVE:

buttonState = STATE_MOVE;

mDistance = event.getX();

Log.d("MOVE","移动");

invalidate();

break;

case MotionEvent.ACTION_UP:

mDistance = event.getX();

buttonState = STATE_UP;

Log.d("UP","起开");

if (mDistance >= mSwitchButton.getWidth() / 2f){

isOpened = true;

}else {

isOpened = false;

}

if (mListener != null){

mListener.onSwitchChanged(isOpened);

}

invalidate();

break;

default:

break;

}

return true;

}

public void setOnSwitchListener(onSwitchListener listener){

this.mListener = listener;

}

public interface onSwitchListener{

void onSwitchChanged(boolean isOpened);

}

}

DemoActivity:

[java] view
plain copy







package com.lian.switchtogglebutton;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

SwitchButtonView switchButtonView = (SwitchButtonView) findViewById(R.id.switchbutton);

switchButtonView.setOnSwitchListener(new SwitchButtonView.onSwitchListener() {

@Override

public void onSwitchChanged(boolean isOpened) {

if (isOpened) {

Toast.makeText(MainActivity.this, "打开", Toast.LENGTH_SHORT).show();

}else {

Toast.makeText(MainActivity.this, "关闭", Toast.LENGTH_SHORT).show();

}

}

});

}

}

布局:

[html] view
plain copy







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

<RelativeLayout

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

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

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.lian.switchtogglebutton.MainActivity">

<com.lian.switchtogglebutton.SwitchButtonView

android:id="@+id/switchbutton"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

/>

</RelativeLayout>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: