Android自定义View(二)

it2022-05-05  175

前言

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

分析

确定宽高

对一个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滑动的实现)

转载于:https://www.cnblogs.com/jasonkent27/p/5836071.html


最新回复(0)