自己定义View基础篇(二)

自己定义View基础篇(一)

自己定义View原理

我在解说之前,先来看看效果图,有图有真相:(转换gif图片效果太差)

那来看看真实图片:

假设你要更改样式,请改动例如以下图片:

switch_ball

switch_bg

switch_black

switch_bottom

我在这里就不反复解说View与ViewGroup的关系,View的绘制流程。假设你对自己定义View还不甚了解。请看上面几篇文章。

用法

xml文件:

    <com.github.ws.switchbuttonview.widget.SwitchButtonView
xmlns:widget="http://schemas.android.com/apk/res-auto"
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
widget:checked="false" />

widget:checked属性表示默认是关。当然你也能够设置成true。

Activity文件:

        mSwitch= (SwitchButtonView) findViewById(R.id.sbv);
mSwitch.setOnSwitchListener(new SwitchButtonView.onSwitchListener() {
@Override
public void onSwitchChanged(boolean isCheck) {
}
});

绘制流程

自己定义属性

res/values/attrs.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SwitchButtonView">
<attr name="checked" format="boolean"></attr>
</declare-styleable>
</resources>

SwitchButtonView文件:

    public SwitchButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SwitchButtonView);
isChecked = ta.getBoolean(R.styleable.SwitchButtonView_checked, false);
ta.recycle();
init(context);
}

onMeasure()方法

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(switchWidth, switchHeight);
}
switchWidth = bitmapBackGround.getWidth();
        bitmapBackGround = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg);

在文章后面我会贴出源代码,以及源代码地址供大家參考。了解了原理你想设计成什么样的开关都不是问题。

这个控件并不须要控制其摆放位置,不须要重写onLayout()方法。

onDraw()方法确定其形状

手指点击控件。依据触摸的状态来改变Switch的状态,须要重写onTouchEvent()方法,而且返回true。有的同学就会问了,问什么返回true呢?返回false不行吗?请看Android Touch事件传递

手指按下MotionEvent.ACTION_DOWN,获取手指的X坐标(event.getX())。由于Y坐标是不变的。全部我们不须要考虑。

这里要解说下event.getX()。和event.getRawX()。event.getX()是相对于父控件而言。event.getRawX()相对于屏幕而言。切记他们的值普通情况下是不一样的。当X坐标小于等于0时。小球的X坐标等于0;假设X坐标大于等于(父控件宽度减去小球宽度),那么小球的X坐标等于父控件宽度减去小球宽度。

        ballX = touchX = mTouchX;
if (touchX <= 0) {
ballX = 0;
}
if (touchX >= switchWidth - bitmapBall.getWidth()) {
ballX = switchWidth - bitmapBall.getWidth();
}

手指移动MotionEvent.ACTION_MOVE跟手指按下的情况是一样的。

手指抬起MotionEvent.ACTION_UP的2种情况,手指抬起时X坐标小于父控件的二分之中的一个,Switch处于关闭状态;X坐标大于父控件的二分之中的一个,Switch处于开启状态:

            if (touchX >= switchWidth / 2f) {
isChecked = true;
ballX = switchWidth - bitmapBall.getWidth();
} else {
isChecked = false;
ballX = 0;
}

已经获取到手指的一个状态,那么依据手指的状态去绘制小球的位置。

TOUCH_STATE_DOWN。TOUCH_STATE_MOVE是一样的,小球的位置有三种情况。

                if (touchX > 0 && touchX < switchWidth - bitmapBall.getWidth()) {    //小球可运动区域
canvas.drawBitmap(bitmapBall, touchX, 0, mPaint);
} else if (touchX <= 0) { //触摸到父控件之外的左边
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) { //触摸到父控件减去小球宽度的右边
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}

TOUCH_STATE_UP,LEFT_MOST(关闭)状态下有四种情况,他们各自是:

                if (touchX > 0 && touchX < switchWidth / 2) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth / 2 && touchX <= switchWidth) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
} else if (touchX <= 0) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}

处了父控件之外的左边和右边之外,还多出了父控件二分之中的一个的左边和右边。

RIGHT_MOST(开启)状态下小球的位置:

canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);

接下来在绘制过程中会遇到图层的问题。还记得開始那张黑黑的图片吗。

有关图层请点击这里

绘制新的图层:

 canvas.saveLayer(0, 0, switchWidth, switchHeight, null, saveFlags);

然后我们把黑黑的图片绘制到新的图层上面,而且取新旧图层重叠的旧的图层部分。

 canvas.drawBitmap(bitmapBlack, 0, 0, mPaint);
mPaint.setXfermode(pdf);

继续绘制底部的那张图片(switch_bottom),依据开,关状态绘制。

        if (isChecked) {
canvas.drawBitmap(bitmapBottom, 0 - (switchWidth - bitmapBall.getWidth() - ballX), 0, mPaint);
} else {
canvas.drawBitmap(bitmapBottom, -(bitmapBottom.getWidth() / 2 - bitmapBall.getWidth() / 2) + ballX, 0, mPaint);
}

最后调用canvas.restore();完毕图层的绘制。

到这里onDraw()方法解说的几乎相同了,后面的接口我就不啰嗦了。详细请看源代码:

package com.github.ws.switchbuttonview.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View; import com.github.ws.switchbuttonview.R; /**
* Created by Administrator on 3/22 0022.
*/
public class SwitchButtonView extends View {
private Paint mPaint;
//背景
private Bitmap bitmapBackGround;
//小球
private Bitmap bitmapBall;
//底部
private Bitmap bitmapBottom;
//黑色
private Bitmap bitmapBlack;
//取重叠部分
private PorterDuffXfermode pdf;
//开关状态
private boolean isChecked;
//触摸X坐标
private int touchX;
//小球X坐标
private int ballX = 0;
//小球运动状态
private int ballMoveState = LEFT_MOST;
//图层标识
private int saveFlags;
//switch的宽度
private int switchWidth;
//switch的高度
private int switchHeight;
//最左边
private static final int LEFT_MOST = 0;
//最右边
private static final int RIGHT_MOST = 1;
//手指按下
private static final int TOUCH_STATE_DOWN = 2;
//手指移动
private static final int TOUCH_STATE_MOVE = 3;
//手指抬起
private static final int TOUCH_STATE_UP = 4; private onSwitchListener mListener; public SwitchButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SwitchButtonView);
isChecked = ta.getBoolean(R.styleable.SwitchButtonView_checked, false);
ta.recycle();
init(context);
} public SwitchButtonView(Context context) {
this(context, null);
} private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true); pdf = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); //2张重叠 取上面一张重叠部分 saveFlags = Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG; bitmapBackGround = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg);
bitmapBall = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_ball);
bitmapBottom = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bottom);
bitmapBlack = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_black); switchWidth = bitmapBackGround.getWidth();
switchHeight = bitmapBackGround.getHeight();
//开
if (isChecked) {
ballMoveState = RIGHT_MOST;
ballX = bitmapBackGround.getWidth() - bitmapBall.getWidth();
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(switchWidth, switchHeight);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//添加图层
canvas.saveLayer(0, 0, switchWidth, switchHeight, null, saveFlags);
//底部是黑色图层
canvas.drawBitmap(bitmapBlack, 0, 0, mPaint);
mPaint.setXfermode(pdf);
if (isChecked) {
canvas.drawBitmap(bitmapBottom, 0 - (switchWidth - bitmapBall.getWidth() - ballX), 0, mPaint);
} else {
canvas.drawBitmap(bitmapBottom, -(bitmapBottom.getWidth() / 2 - bitmapBall.getWidth() / 2) + ballX, 0, mPaint);
}
mPaint.setXfermode(null);
canvas.restore();
ballMoveState(canvas);
} /**
* 滑动状态绘制
*
* @param canvas
*/
private void ballMoveState(Canvas canvas) {
switch (ballMoveState) {
case TOUCH_STATE_DOWN:
case TOUCH_STATE_MOVE:
if (touchX > 0 && touchX < switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, touchX, 0, mPaint);
} else if (touchX <= 0) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}
break;
case TOUCH_STATE_UP:
case LEFT_MOST:
if (touchX > 0 && touchX < switchWidth / 2) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth / 2 && touchX <= switchWidth) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
} else if (touchX <= 0) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}
break;
case RIGHT_MOST:
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
break;
default:
break;
}
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStateChange((int) event.getX(), TOUCH_STATE_DOWN);
break;
case MotionEvent.ACTION_MOVE:
touchStateChange((int) event.getX(), TOUCH_STATE_MOVE);
break;
case MotionEvent.ACTION_UP:
touchStateChange((int) event.getX(), TOUCH_STATE_UP);
break;
default:
break;
}
return true;
}
/**
* 触摸状态改变
*
* @param mTouchX
* @param touchState
*/
private void touchStateChange(int mTouchX, int touchState) {
ballX = touchX = mTouchX;
if (touchX <= 0) {
ballX = 0;
}
if (touchX >= switchWidth - bitmapBall.getWidth()) {
ballX = switchWidth - bitmapBall.getWidth();
}
ballMoveState = touchState;
if (ballMoveState == TOUCH_STATE_UP) { //手指抬起
ballX = 0;
if (touchX >= switchWidth / 2f) {
isChecked = true;
ballX = switchWidth - bitmapBall.getWidth();
} else {
isChecked = false;
}
if (mListener != null) {
mListener.onSwitchChanged(isChecked);
}
}
invalidate();
} public void setOnSwitchListener(onSwitchListener listener) {
this.mListener = listener;
} public interface onSwitchListener {
void onSwitchChanged(boolean isCheck);
} }

源代码下载

Android自己定义View基础篇(三)之SwitchButton开关的更多相关文章

  1. Android 自己定义View (二) 进阶

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自己定义View之旅.前面已经介绍过一个自己定义View的基础 ...

  2. Android Fragment使用(一) 基础篇 温故知新

    Fragment使用的基本知识点总结, 包括Fragment的添加, 参数传递和通信, 生命周期和各种操作. Fragment使用基础 Fragment添加 方法一: 布局里的标签 标识符: tag, ...

  3. Android 自己定义View学习(2)

    上一篇学习了基本使用方法,今天学一下略微复杂一点的.先看一下效果图 为了完毕上面的效果还是要用到上一期开头的四步 1,属性应该要有颜色,要有速度 <?xml version="1.0& ...

  4. Android 自己定义View须要重写ondraw()等方法

    Android  自己定义View须要重写ondraw()等方法.这篇博客给大家说说自己定义View的写法,须要我们继承View,然后重写一些 方法,方法多多,看你须要什么方法 首先写一个自己定义的V ...

  5. 【Android自己定义View实战】之自己定义超简单SearchView搜索框

    [Android自己定义View实战]之自己定义超简单SearchView搜索框 这篇文章是对之前文章的翻新,至于为什么我要又一次改动这篇文章?原因例如以下 1.有人举报我抄袭,原文链接:http:/ ...

  6. 自己定义 View 基础和原理

    课程背景: 在 Android 提供的系统控件不能满足需求的情况下,往往须要自己开发自己定义 View 来满足需求,可是该怎样下手呢.本课程将带你进入自己定义 View 的开发过程,来了解它的一些原理 ...

  7. Android自己定义view之measure、layout、draw三大流程

    自己定义view之measure.layout.draw三大流程 一个view要显示出来.须要经过測量.布局和绘制这三个过程,本章就这三个流程具体探讨一下.View的三大流程具体分析起来比較复杂,本文 ...

  8. 手把手带你画一个 时尚仪表盘 Android 自己定义View

    拿到美工效果图.咱们程序猿就得画得一模一样. 为了不被老板喷,仅仅能多练啊. 听说你认为前面几篇都so easy,那今天就带你做个相对照较复杂的. 转载请注明出处:http://blog.csdn.n ...

  9. Android自己定义View的实现方法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了.回 ...

随机推荐

  1. Day05基本运算符,if判断和while循环

    day05 1.常量 变量名全大写 2.基本运算符 ①算术运算 10/3除法 10//3取整 10*3乘法 10**3幂 ②赋值运算 增量赋值 age += 1#age = age + 1 age * ...

  2. vfs_caches_init函数解析

    vfs_caches_init函数初始化VFS,下面梳理函数调用流程 start_kernel() -->vfs_caches_init_early(); -->dcache_init_e ...

  3. ACM-ICPC 2018 沈阳赛区网络预赛 F. Fantastic Graph

    "Oh, There is a bipartite graph.""Make it Fantastic." X wants to check whether a ...

  4. bash中的算术运算

    bash中的算术运算     +, -, *, /, %     实现算术运算:         (1) let var=算术表达式          (2) var=$[算术表达式]         ...

  5. 在xcode上把你的app多语言国际化(NSLocalizedString)

    1.到project->info->localizations   下面的加号,添加你需要的语言 千万不要删除  base 否虽然我不知道有什么用,我是删了整个storyboard没了,很 ...

  6. Ubuntu Software Center has closed unexpectly解决方案

    打开软件中心Ubuntu Software Center的时候 出现crash report :The application Ubuntu Software Center has closed un ...

  7. Codeforces #765D

    我在这道题上花了2个小时,仍没解出.理一下当时的思路,看看症结到底在哪里. 题意 用 $[n]$ 表示集合 $\{1,2,3,\dots, n\}$ . 3个函数 $f \colon [n] \to ...

  8. 刷题总结——魔法森林(bzoj3669)

    题目: Description 为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士.魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M.初始时小E同 ...

  9. Lucas 卢卡斯定理

    Lucas: 卢卡斯定理说白了只有一条性质 $$ C^n_m \equiv C^{n/p}_{m/p} \times C^{n \bmod p}_{m \bmod p} \ (mod \ \ p) $ ...

  10. 在App_Data中创建数据库获取连接串简便方法!

    原文发布时间为:2008-07-25 -- 来源于本人的百度文章 [由搬家工具导入] 1、在App_Data右击添加一个SQL数据库2、双击该数据库,在左边添加表,并显示表数据进行添加数据3、把刚刚创 ...