博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 自定义SwitchButtonView实践
阅读量:6567 次
发布时间:2019-06-24

本文共 9260 字,大约阅读时间需要 30 分钟。

hot3.png

1、文本绘制基线测量

文本绘制的方法是Canvas类的drawText,对于x点坐标其实和正常流程类似,但Y坐标的确定需要考虑Baseline问题

@param text The text to be drawn @param x X方向的坐标,开始绘制的左上角横轴坐标点@param y Y坐标,该坐标是Y轴方向上的”基线”坐标@param paint 画笔工具*/ public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

基线到中线的距离=(Descent+Ascent)/2-Descent ,Android中,实际获取到的Ascent是负数。

公式推导过程如下: 

中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。 
有了基线到中线的距离,我们只要知道任何一行文字中线的位置,就可以马上得到基线的位置,从而得到Canvas的drawText方法中参数y的值。

/**     * 计算绘制文字时的基线到中轴线的距离,Android获取中线到基线距离的代码,Paint需要设置文字大小textsize。     *      * @param p     * @param centerY     * @return 基线和centerY的距离     */    public static float getBaseline(Paint p) {        FontMetrics fontMetrics = p.getFontMetrics();        return (fontMetrics.descent - fontMetrics.ascent) / 2 -fontMetrics.descent;    }

说道这里我们只是计算出了基线高度,Y坐标一般区文本高度的中点位置。比如竖直方向,公式为。

Y = centerY + getBaseline(paint); 

此外,对于宽度的测量,一般使用如下方法

mPaint.getTextBounds(text, 0, text.length(), mBounds); float textwidth = mBounds.width();

 

2、Path闭合区域填充问题

在常见的绘制View的过程中,我们通过Path对象构建复杂的闭合图像,最后一般来通过Paint设置Style.FILL填充区域,但是对于闭合的Path填充,在Android某些版本中不支持填充Path的区域。实际上Path同样提供了填充方法,可以做到很好的兼容。

Android的Path.FillType除了支持上面两种模式外,还支持了上面两种模式的反模式,一共定义了EVEN_ODD, INVERSE_EVEN_ODD, WINDING, INVERSE_WINDING 四种模式。

实际上,WINDING类似Paint中的Style.FILL

 

3、Path 图像合成

一般情况下我们图像是将Bitmap合成,合成时使用Xfermodes,当然Path也可以转为Bitmap图像数据。

但是Path同样提供了一系列合成方法

DIFFERENCE:从path1中减去path2

INTERSECT:取path1和path2重合的部分

REVERCE_DIFFERENCE:从path2中减去path1

UNION:联合path1和path2

XOR:取path1和path2不重合的部分

 

4、StrokeWidth与区域大小问题

对于带边框的View,StrokeWidth在很多情况下被认为不挤占区域大小,实际上,与此相反,我们计算坐标时一定要计算线宽问题。比如绘制线宽StrokeWidth的起点矩形,如果不这样计算,绘制将会出现边框宽度不一致的情况。

startX = StrokeWidth;startY = StrokeWidth;endX = getWidth() - StrokeWidth;endY = getHeight- StrokeWidth;

 

5、触摸MOVE事件问题

很多时候绘制View我们需要处理TouchEvent事件,然而,Android中View默认无法监听,需要设置一个莫名其妙的参数。

setClickable(true);

 

5、事件状态转移问题

很多时候,我们判断到某一区域时达到某种条件需要主动结束事件事务,或者改变事件状态如下然后在传递出去,方法如下

MotionEvent actionUP = MotionEvent.obtain(event); //增量式拷贝,比如修修改开始时间、修改修改时间序列  actionUP.setAction(MotionEvent.ACTION_UP);  dispatchTouchEvent(actionUP); //传递事件,注意不要造成死循环问题

 

基于以上问题的解决,实现了一个SwitchButton,虽然没用到Path,但还是考虑了很多问题。

public class SwitchButtonView extends View  {    // 实例化画笔    private TextPaint mPaint = null;    private Path mPath;// 路径对象    private int lineWidth  = 1;    private final  int STATUS_LEFT = 0x00;    private final  int STATUS_RIGHT = 0x01;    private volatile  int mStatus = STATUS_LEFT;    private int textSize = 18;    private volatile float startX = 0; //触摸开始位置    private volatile boolean isTouchState = false;    private volatile float currentX = 0;    private final  String[]  STATUS = {"开","关"};    private OnStatusChangedListener mOnStatusChangedListener;    public void setLeftText(String text){        STATUS[0] = text;    }    public void setRightText(String text){        STATUS[1] = text;    }    public SwitchButtonView(Context context) {        this(context,null);    }    public SwitchButtonView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs,0);    }    public SwitchButtonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initPaint();        setClickable(true); //设置此项true,否则无法滑动    }    private void initPaint() {        // 实例化画笔并打开抗锯齿        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG );        mPaint.setAntiAlias(true);        mPaint.setPathEffect(new CornerPathEffect(10)); //设置线条类型        mPaint.setStrokeWidth(dpTopx(lineWidth));        mPaint.setTextSize(dpTopx(textSize));    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int width = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int height = MeasureSpec.getSize(heightMeasureSpec);        if(widthMode!=MeasureSpec.EXACTLY){            width = (int) dpTopx(105*2);        }        if(heightMode!=MeasureSpec.EXACTLY){            height = (int) dpTopx(35*2);        }        setMeasuredDimension(width,height);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int width = getWidth();        int height = getHeight();        if(width<=0 || height<=0) return;        int centerX = width/2;        int centerY = height/2;        int lineWidthPixies = (int) dpTopx(lineWidth);        int R = getHeight()/2;        mPaint.setStyle(Paint.Style.STROKE);        int startX = lineWidthPixies;        int startY = lineWidthPixies;        int endX = width - 2*lineWidthPixies; //宽度应该减去左右两边的线宽        int endY = height - 2*lineWidthPixies; //宽度应该减去上下两边的线宽        canvas.drawRoundRect(new RectF(startX,startY,endX,endY),R,R,mPaint);        //中间分割线        canvas.drawLine(centerX,height*2/5,centerX,height*3/5,mPaint);        drawText(canvas, width, centerY);        drawSlider(canvas,width,height,lineWidthPixies);    }    private void drawText(Canvas canvas, int width, int centerY) {        Rect mBounds = new Rect();        mPaint.getTextBounds(STATUS[0], 0, STATUS[0].length(), mBounds);        float textwidth = mBounds.width();        float textBaseline = centerY + getTextPaintBaseline(mPaint);        mPaint.setStyle(Paint.Style.FILL);        canvas.drawText(STATUS[0],width/4-textwidth/2,textBaseline,mPaint);        canvas.drawText(STATUS[1],width*3/4-textwidth/2, textBaseline,mPaint);//文本位置以基线为准        mPaint.setStyle(Paint.Style.STROKE);    }    /**     * 基线到中线的距离=(Descent+Ascent)/2-Descent     注意,实际获取到的Ascent是负数。公式推导过程如下:     中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。     */    public static float getTextPaintBaseline(Paint p) {        Paint.FontMetrics fontMetrics = p.getFontMetrics();        return (fontMetrics.descent - fontMetrics.ascent) / 2 -fontMetrics.descent;    }    private void drawSlider(Canvas canvas, int outwidth, int outheight, int lineWidthPixies) {        int color = mPaint.getColor();        mPaint.setColor(Color.GREEN);        mPaint.setStyle(Paint.Style.FILL);        float width = outwidth - 2*lineWidthPixies;        float height = outheight - 2 * lineWidthPixies;        int slideBarX =  2* lineWidthPixies;        int slideBarY =  2*lineWidthPixies;        int R = (int) (height/2);        if(isTouchState){            canvas.drawRoundRect(new RectF(currentX, slideBarY, currentX+width/2-3*lineWidthPixies, height - lineWidthPixies), R, R, mPaint);        }else {            if (mStatus == STATUS_RIGHT) {                slideBarX = (int) (slideBarY+width/2+lineWidthPixies);                canvas.drawRoundRect(new RectF(slideBarX, slideBarY, width - lineWidthPixies, height - lineWidthPixies), R, R, mPaint);            } else {                canvas.drawRoundRect(new RectF(slideBarX, slideBarY, width / 2 - lineWidthPixies, height - lineWidthPixies), R, R, mPaint);            }        }        mPaint.setColor(color);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        float lineWidthPixies = dpTopx(lineWidth);        float width = (getWidth()- 2*lineWidthPixies);        float sliderWidth = width/2;        int actionMasked = event.getActionMasked();        switch (actionMasked){            case MotionEvent.ACTION_DOWN: {                isTouchState = true;                startX = event.getX();                if (startX > (width / 2) && startX<(width-lineWidthPixies) && mStatus == STATUS_LEFT) {                    MotionEvent actionUP = MotionEvent.obtain(event);                    actionUP.setAction(MotionEvent.ACTION_UP);                    dispatchTouchEvent(actionUP);                } else if (startX > lineWidthPixies && (startX < width / 2 && mStatus == STATUS_RIGHT)) {                    MotionEvent actionUP = MotionEvent.obtain(event);                    actionUP.setAction(MotionEvent.ACTION_UP);                    dispatchTouchEvent(actionUP);                }else if(startX
(width-lineWidthPixies)){ MotionEvent actionOUTSIDE = MotionEvent.obtain(event); actionOUTSIDE.setAction(MotionEvent.ACTION_OUTSIDE); dispatchTouchEvent(actionOUTSIDE); } } break; case MotionEvent.ACTION_MOVE: currentX = event.getX()- sliderWidth/2; //滑块移动位置应该相对于中心位置为基准 if(currentX<(2* lineWidthPixies)){ currentX = 2* lineWidthPixies; //最左边 }else if(currentX>((lineWidthPixies+sliderWidth)+2*lineWidthPixies)){ //最右边 currentX = (sliderWidth)+2*lineWidthPixies; } postInvalidate(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_OUTSIDE: isTouchState = false; float xPos = event.getX(); if((xPos>width/2&& mStatus==STATUS_LEFT)){ mStatus = STATUS_RIGHT; onStatusChanged(mStatus); }else if(xPos>lineWidthPixies && (xPos

 

转载于:https://my.oschina.net/ososchina/blog/3021732

你可能感兴趣的文章
jQuery hover() 方法
查看>>
android 一步一步教你集成tinker(热修复)
查看>>
到底有多少内存
查看>>
centos7.3 安装ovirt-engine4.0 版本
查看>>
Jenkins+git+tomcat 自动化持续部署
查看>>
项目log日志打印
查看>>
Openstack的环境的Mitaka部署环境服务,实例(1)
查看>>
文档的压缩与打包
查看>>
python3 在不同操作系统安装第三方库方法
查看>>
redhat5.8+mfs(提供软件包文档)
查看>>
python编写登录接口
查看>>
MySQL高可用方案之多级复制
查看>>
OVS 中的各种网络设备 - 每天5分钟玩转 OpenStack(128)
查看>>
Python火车票代码
查看>>
Android开发者指南(7) —— App Install Location
查看>>
Trafficserver Cluster模式
查看>>
亚马逊推出 Blox,用于 EC2 容器服务的开源工具集合
查看>>
Linux:在中国没有真正的新闻
查看>>
iOS推送功能极光推送的介绍与实现
查看>>
单用户模式与grub加密
查看>>