Android自定义View(二)

前言

魅族手机的闹钟应用中有个倒计时,这个控件还是蛮有趣的。左边是魅族闹钟,右边是我们最终实现的效果,虽然有些细节还需优化,不过基本上已经达到了想要的效果,我们先来就来看看如何实现吧。

分析

确定宽高

对一个Android自定义控件来说,一般都经过三个步骤

  • onLayout()
  • onMeasure()
  • onDraw()

onLayout明确子控件在父控件中的位置(本控件不需要重写),onMeasure是确定控件的大小(宽、高),而onDraw是我们重点关注的方法,我们需要在这个方法中写入显示View的逻辑代码。

对于本控件,控件的高度 应该等于细线的高度(mLineHeight)加上数字的高度(mFontHeight),当然为了好看,中间需要设上一些边距(mPadding),因此本控件的高度应该为 mFontHeight + mLineHeight + 10 + mPadding,测量代码如下

    private int measureHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        switch (mode) {
            case MeasureSpec.EXACTLY:
                return size ;
            case MeasureSpec.AT_MOST:
                return Math.min(size, mFontHeight + mLineHeight + 10 + mPadding) ;
        }
        return size ;
    }

同样地,控件的宽度其实就是0~1000的间隔,测量代码如下

    private int measureWidth(int widthMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        switch (mode) {
            case MeasureSpec.EXACTLY:
                return size ;
            case MeasureSpec.AT_MOST:
                int result = getPaddingLeft() + mContentWidth + getPaddingRight() ;
                return Math.min(size, result) ;
        }
        return size ;
    }

画刻度尺

重点在于刻度尺的计算。思路是先draw上头的数字,然后再draw下边的线条,判断位置确定是否需要draw上头的数字即可。其实就是坐标的计算。代码如下

        int startX = mPadding;
        int stopX = mPadding;
        int stopY =mHeight - mPadding;
        for (int i = 0 ; i<=mContentWidth ; i += mFontWidth)
        if (i % (mFontWidth *10) == 0) {
            canvas.drawLine(startX + i, mFontHeight + mPadding + 5 , stopX + i, stopY, mTextPaint);
            canvas.drawText(i + "", startX + i, mFontHeight + mPadding, mTextPaint);
        } else if (i % mFontWidth == 0) {
            canvas.drawLine(startX + i, mFontHeight + mPadding + 10, stopX + i, stopY, mPaint);
        }

让View动起来

Android本身提供了移动View的API,因此让View动起来也是不难的。两种思路

  • 监听Touch事件,当Touch坐标变化时,计算坐标位置,不断调用scrollTo(x,0)达到变换坐标的目的
  • 监听Touch事件,记录上次横坐标和本次横坐标的差值,然后调用scrollBy(delta, 0) 即可移动

其实两种方法本质上都是一样的。ScrollBy其实也是调用了scrollTo方法。本文采用方法二。其代码如下

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int x = (int) event.getX();
                int deltaX = x - mLastX;
                scrollBy(-deltaX, 0);
                mLastX = x;
                break;
        }
        return true;
    }

当然了,我们是不能让View无限移动的,因此需要重写scrollBy方法,限制View不能超过边界。

代码如下

    @Override
    public void scrollBy(int x, int y) {
        super.scrollBy(x, y);
        if (x < 0) { // drag to left
            if (getScrollX() < -getCenter() + mPadding) {
                scrollTo(-getCenter() + mPadding, 0);
            }
        } else
        if (x >0) { // drag to right
           if (mContentWidth - getScrollX() + x < getCenter()) {
                scrollTo(mContentWidth - getCenter() + mFontWidth, 0);
           }
        }
    }

当超过边界时,直接调用scrollTo,让View停留在特定的位置即可。需要注意的一点是,View往左滑动时,ScrollX的值是负的。

完整代码

package com.nancyyihao.demo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

public class RulerView extends View {

    private static final String TAG = RulerView.class.getSimpleName() ;
    private TextPaint mTextPaint;
    private Paint mPaint ;

    private int mWidth;
    private int mHeight;
    private int mPadding = 10;
    private Scroller mScroller  ;
    private int mLastX;
    private int mContentWidth = 1000;
    private int mLineHeight = 50;
    private int mFontHeight;
    private int mFontWidth = 10;

    private onValueChangedListener mValueChangedListener;

    public interface onValueChangedListener {
        void onValueChanged(int newValue);
    }

    public RulerView(Context context) {
        super(context);
        init(context);
    }

    public RulerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public RulerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public void setOnValueChangedListener(onValueChangedListener listener) {
        this.mValueChangedListener = listener ;
    }

    private void init(Context context) {
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(Color.parseColor("#FF4081"));
        mTextPaint.setTextSize(30);
        mTextPaint.setStrokeWidth(2f);
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        mFontHeight = Math.round(Math.abs(fontMetrics.top) + Math.abs(fontMetrics.bottom)) ;

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setColor(Color.DKGRAY);
        mPaint.setStrokeWidth(2f);
        mPaint.setTextSize(30);
        mPaint.setTextAlign(Paint.Align.CENTER);

        //mScroller = new Scroller(context) ;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mWidth = measureWidth(widthMeasureSpec);
        mHeight = measureHeight(heightMeasureSpec);
        setMeasuredDimension(mWidth , mHeight );
    }

    private int measureWidth(int widthMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        switch (mode) {
            case MeasureSpec.EXACTLY:
                return size ;
            case MeasureSpec.AT_MOST:
                int result = getPaddingLeft() + mContentWidth + getPaddingRight() ;
                return Math.min(size, result) ;
        }
        return size ;
    }

    private int measureHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        switch (mode) {
            case MeasureSpec.EXACTLY:
                return size ;
            case MeasureSpec.AT_MOST:
                return Math.min(size, mFontHeight + mLineHeight + 10 + mPadding) ;
        }
        return size ;
    }

//    private void smoothScrollTo(int destX, int destY) {
//        int scrollX = getScrollX() ;
//        int delta = destX - scrollX ;
//        mScroller.startScroll(scrollX, 0, delta, 0 , 1000);
//        invalidate();
//    }
//
//    @Override
//    public void computeScroll() {
//        super.computeScroll();
//        if (mScroller.computeScrollOffset()) {
//            smoothScrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//            postInvalidate();
////            ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
////            invalidate();
//        }
//    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int startX = mPadding;
        int stopX = mPadding;
        int stopY =mHeight - mPadding;
        for (int i = 0 ; i<=mContentWidth ; i += mFontWidth)
        if (i % (mFontWidth *10) == 0) {
            canvas.drawLine(startX + i, mFontHeight + mPadding + 5 , stopX + i, stopY, mTextPaint);
            canvas.drawText(i + "", startX + i, mFontHeight + mPadding, mTextPaint);
        } else if (i % mFontWidth == 0) {
            canvas.drawLine(startX + i, mFontHeight + mPadding + 10, stopX + i, stopY, mPaint);
        }
    }

    private int calcValue() {
        return  ( getCenter() + getScrollX() - mPadding) ; //minus startX
    }

    private int getCenter() {
        return (getRight() - getLeft()) / 2 ;
    }

    @Override
    public void scrollBy(int x, int y) {
        super.scrollBy(x, y);
        if (x < 0) { // drag to left
            if (getScrollX() < -getCenter() + mPadding) {
                scrollTo(-getCenter() + mPadding, 0);
            }
        } else
        if (x >0) { // drag to right
           if (mContentWidth - getScrollX() + x < getCenter()) {
                scrollTo(mContentWidth - getCenter() + mFontWidth, 0);
           }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int x = (int) event.getX();
                int deltaX = x - mLastX;
                scrollBy(-deltaX, 0);
                mLastX = x;
                if (mValueChangedListener != null)
                    mValueChangedListener.onValueChanged(calcValue());
                break;
        }
        return true;
    }
}

总结

整体上还是比较粗糙,原形虽然有了,但是还需要优化。

参考

【Android】自定义View —— 滑动的次数选择器

android 滚轮刻度尺的实现

Android View自定义专题二(View滑动的实现)

时间: 2024-08-01 15:09:13

Android自定义View(二)的相关文章

Android 自定义View (二) 进阶

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例子,Android 自定义View (一),如果你还对自定义View不了解可以去看看.今天给大家带来一个稍微复杂点的例子. 自定义View显示一张图片,下面包含图片的文本介绍,类似相片介绍什么的,不过不重要,主要是学习自定义View的用法么. 还记得上一篇讲的4个步骤么: 1.自定义View的属性2

Android 自定义View (二) 圆环交替 等待效果

我们在下载的时候需要一个下载的进度,而且可能产品要一个漂亮的界面,而不是android自带的进度条了,在这感谢http://blog.csdn.net/lmj623565791/article/details/24500107博客的无私奉献 废话不多说,直接切入主题 先新建一个android项目:CustomProgressBar 还记得自定义view的步骤么 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 [ 3.重写onMesure ] 4.重写onDraw A:

Android自定义view学习笔记02

Android自定义view学习笔记02 本文代码来自于张鸿洋老师的博客之Android 自定义View (二) 进阶 学习笔记,对代码进行些许修改,并补充一些在coding过程中遇到的问题.学习的新东西. 相关代码 //CustomImageView.java package mmrx.com.myuserdefinedview.textview; import android.content.Context; import android.content.res.TypedArray; im

自定义View(二)(Android群英传)

内容是博主照着书敲出来的,博主码字挺辛苦的,转载请注明出处,后序内容陆续会码出. 上一篇自定义View(一)(Android群英传)中说的是对现有控件进行拓展,这篇介绍第二种自定义View方法,创建复合控件. 创建复合控件 创建复合控件可以很好地创建出具有重用功能的控件集合.这种方式通常需要继承一个合适的ViewGroup,再给它添加指定功能的控件,从而组合成新的复合控件.通过这种方式创建的控件,我们一般会给它指定一些可配置的属性,让它具有更强的拓展性.下面就以一个TopBar为示例,讲解如何创

Android自定义View(二、深入解析自定义属性)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 属性值的类型format 类中获取属性值 Attributeset和TypedArray以及declare-styleable ??在上一篇博客<Android自定义View(一.初体验)>中我们体验了自定义控件的基本流程: 继承View,覆盖构造方法 自定义属性 重写onMeasure方法测量宽

android 自定义View【2】对话框取色&amp;色盘取色的实现

android 自定义View[2]对话框取色&色盘取色的实现    上一篇文章基本介绍了android自定义view的流程:继承view,复写view的一些方法.实现简单的自定义view.这篇文章主要介绍的是系统对话框取色功能,然后顺便介绍升级版,色盘取色[类似于ps中的吸管,对图片点击相应位置,获取那个位置的颜色]. 一.概述:通过该例子了解以下内容: 1.进一步了解android 自定义view. 2.知道如何获取图片上的颜色值. 3.监听屏幕touch,实现移动的时候自动取色.[onDr

Android 自定义 view(三)&mdash;&mdash; onDraw

前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自定义view的两个比较重要的方法 onDraw(Canvas canvas) ,在探究 onDraw方法之前,我们必须先深入了解两个类Paint和Canvas .   第一:认识Paint 在探究onDraw之前首先必须要认识两个类,这里给出非常不错的两个资料参考网站,我也是从这里得到想要知道的东西,简单的说

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android自定义View,你必须知道的几点

为什么我们觉得自定义View是学习Android的一道坎? 为什么那么多Android大神却认为自定义View又是如此的简单? 为什么google随便定义一个View都是上千行的代码? 以上这些问题,相信学Android的同学或多或少都有过这样的疑问. 那么,看完此文,希望对你们的疑惑有所帮助. 回到主题,自定义View ,需要掌握的几个点是什么呢? 我们先把自定义View细分一下,分为两种 1) 自定义ViewGroup 2) 自定义View 其实ViewGroup最终还是继承之View,当然

android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索

我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的Android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体上来说,左边是一个ListView,右边是一个自定义View,但是左边的ListView和我们平常使用的ListView还有一点点不同,就是在ListView中我对所有的联系人进行了分组,那么这种效果的实现最常见的就是两种思路: 1.使用ExpandableListView来实现这种分组效果 2.使