第一篇搞定了UI,这一篇来处理下事件,ANDROID WHEEL有响应onscroll和onfling事件,这个该如何处理?这里有几个小知识点:
1.ontouchevent中调用GestureDetector.onTouchEvent(event)这个方法作为返回值,所以调用顺序就是ontouchevent–>onscroll或者ontouchevent–>onfling。
2.View.post(Runnable)也是修改UI的一种方式。
好了,上代码。
public class Wheel extends View implements GestureDetector.OnGestureListener,IFlingRunnable.FlingRunnableView {
private int mTicksCount = 18;
private float mTicksSize = 7.0f;
private float mIndicatorX = 0;
private Bitmap mIndicator;
private Bitmap mTickBitmap;
private Shader mShader3;
private Matrix mDrawMatrix = new Matrix();
int mOriginalDeltaX = 0;
private Paint mPaint;
int mWidth, mHeight;
float mTickSpace = 30;
private Easing mTicksEasing = new Sine();
private GestureDetector mGestureDetector;
private boolean mToLeft;
private int mMaxX, mMinX;
private int mWheelSizeFactor = 2;
private IFlingRunnable mFlingRunnable;
private int mAnimationDuration = 200;
private boolean mIsFirstScroll;
private int mTouchSlop;//这个值是判断是否为scroll的一个阈值,一般为8dp
public Wheel(Context context) {
this(context, null);
}
public Wheel(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Wheel(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
Log.d("111", "wheel init");
//构造一个Runnable,VIEW.POST(Runnable)这个方法
mFlingRunnable = new Fling8Runnable( this, mAnimationDuration );
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Wheel, defStyleAttr, 0);
mTicksCount = a.getInteger(R.styleable.Wheel_ticks, 18);
mWheelSizeFactor = a.getInteger(R.styleable.Wheel_numRotations, 2);
a.recycle();
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(false);
setFocusable(true);
setFocusableInTouchMode(true);
mTouchSlop = ViewConfiguration.get( context ).getScaledTouchSlop();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mWidth = right - left;
mHeight = bottom - top;
mTickSpace = (float) mWidth / mTicksCount;
mTicksSize = mWidth / mTicksCount / 4.0f;
mTicksSize = Math.min(Math.max(mTicksSize, 3.5f), 6.0f);
mIndicatorX = (float) mWidth / 2.0f;
mIndicator = makeBitmapIndicator((int) Math.ceil(mTicksSize), bottom - top);
mTickBitmap = makeTickerBitmap((int) Math.ceil(mTicksSize), bottom - top);
mShader3 = new BitmapShader(makeBitmap3(right - left, bottom - top), Shader.TileMode.CLAMP, Shader.TileMode.REPEAT);
mMaxX = mWidth * mWheelSizeFactor;
mMinX = -mMaxX;
}
private Bitmap makeBitmap3(int width, int height) {
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
int colors[] = {0xdd000000, 0x00000000, 0x00000000, 0xdd000000};
float positions[] = {0f, 0.2f, 0.8f, 1f};
LinearGradient gradient = new LinearGradient(0, 0, width, 0, colors, positions, Shader.TileMode.REPEAT);
p.setShader(gradient);
p.setDither(true);
c.drawRect(0, 0, width, height, p);
return bm;
}
@Override
protected void onDraw(Canvas canvas) {
final int w = mWidth;
int total = mTicksCount;
float x2;
float scale, scale2;
mPaint.setShader(null);
for (int i = 0; i < total; i++) {
float x = (mOriginalDeltaX + (((float) i / total) * w));
if (x < 0) {
x = w - (-x % w);
} else {
x = x % w;
}
scale = (float) mTicksEasing.easeInOut(x, 0, 1.0, mWidth);
scale2 = (float) (Math.sin(Math.PI * (x / mWidth)));
mDrawMatrix.reset();
mDrawMatrix.setScale(scale2, 1);
mDrawMatrix.postTranslate((int) (scale * mWidth) - (mTicksSize / 2), 0);
canvas.drawBitmap(mTickBitmap, mDrawMatrix, mPaint);
}
float indicatorx = (mIndicatorX + mOriginalDeltaX);
if (indicatorx < 0) {
indicatorx = (mWidth * 2) - (-indicatorx % (mWidth * 2));
} else {
indicatorx = indicatorx % (mWidth * 2);
}
if (indicatorx > 0 && indicatorx < mWidth) {
x2 = (float) mTicksEasing.easeInOut(indicatorx, 0, mWidth, w);
scale2 = (float) (Math.sin(Math.PI * (indicatorx / mWidth)));
mDrawMatrix.reset();
mDrawMatrix.setScale(scale2, 1);
mDrawMatrix.postTranslate(x2 - (mTicksSize / 2), 0);
canvas.drawBitmap(mIndicator, mDrawMatrix, mPaint);
}
mPaint.setShader(mShader3);
canvas.drawPaint(mPaint);
}
private Bitmap makeTickerBitmap(int width, int height) {
float ellipse = width / 2;
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setDither(true);
p.setColor(0xFF888888);
float h = (float) height;
float y = (h + 10.0f) / 10.0f;
float y2 = y * 2.5f;
RectF rect = new RectF(0, y, width, height - y2);
c.drawRoundRect(rect, ellipse, ellipse, p);
p.setColor(0xFFFFFFFF);
rect = new RectF(0, y2, width, height - y);
c.drawRoundRect(rect, ellipse, ellipse, p);
p.setColor(0xFFCCCCCC);
rect = new RectF(0, y + 2, width, height - (y + 2));
c.drawRoundRect(rect, ellipse, ellipse, p);
return bm;
}
private Bitmap makeBitmapIndicator(int width, int height) {
float ellipse = width / 2;
float h = (float) height;
float y = (h + 10.0f) / 10.0f;
float y2 = y * 2.5f;
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setDither(true);
p.setColor(0xFF666666);
RectF rect = new RectF(0, y, width, height - y2);
c.drawRoundRect(rect, ellipse, ellipse, p);
p.setColor(0xFFFFFFFF);
rect = new RectF(0, y2, width, height - y);
c.drawRoundRect(rect, ellipse, ellipse, p);
rect = new RectF(0, y + 2, width, height - (y + 2));
int colors[] = {0xFF0076E7, 0xFF00BBFF, 0xFF0076E7};
float positions[] = {0f, 0.5f, 1f};
LinearGradient gradient = new LinearGradient(0, 0, width, 0, colors, positions, Shader.TileMode.REPEAT);
p.setShader(gradient);
c.drawRoundRect(rect, ellipse, ellipse, p);
return bm;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("111", "onTouchEvent");
return mGestureDetector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
//返回值为true
mIsFirstScroll = true;
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.d("111", "onScroll");
if ( mIsFirstScroll ) {
if ( distanceX > 0 )
distanceX -= mTouchSlop;
else
distanceX += mTouchSlop;
}
mIsFirstScroll = false;
float delta = -1 * distanceX;
mToLeft = delta < 0;
if (!mToLeft) {
if (mOriginalDeltaX + delta > mMaxX) {
delta /= (((float) mOriginalDeltaX + delta) - mMaxX) / 10;
}
} else {
if (mOriginalDeltaX + delta < mMinX) {
delta /= -(((float) mOriginalDeltaX + delta) - mMinX) / 10;
}
}
trackMotionScroll((int) (mOriginalDeltaX + delta));
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d("111", "onFling");
boolean toleft = velocityX < 0;
if ( !toleft ) {
if ( mOriginalDeltaX > mMaxX ) {
mFlingRunnable.startUsingDistance( mOriginalDeltaX, mMaxX - mOriginalDeltaX );
return true;
}
} else {
if ( mOriginalDeltaX < mMinX ) {
mFlingRunnable.startUsingDistance( mOriginalDeltaX, mMinX - mOriginalDeltaX );
return true;
}
}
mFlingRunnable.startUsingVelocity( mOriginalDeltaX, (int) velocityX / 2 );
return true;
}
@Override
public void scrollIntoSlots() {
}
@Override
public void trackMotionScroll(int newX) {
mOriginalDeltaX = newX;
invalidate();
}
@Override
public int getMinX() {
return mMinX;
}
@Override
public int getMaxX() {
return mMaxX;
}
}
下面是IFlingRunnable
package thepoor.com.testmywheel;
abstract class IFlingRunnable implements Runnable {
public static interface FlingRunnableView {
boolean removeCallbacks(Runnable action);
boolean post(Runnable action);
void scrollIntoSlots();
void trackMotionScroll(int newX);
int getMinX();
int getMaxX();
}
protected int mLastFlingX;
protected boolean mShouldStopFling;
protected FlingRunnableView mParent;
protected int mAnimationDuration;
protected static final String LOG_TAG = "fling";
public IFlingRunnable(FlingRunnableView parent, int animationDuration ) {
mParent = parent;
mAnimationDuration = animationDuration;
}
public int getLastFlingX() {
return mLastFlingX;
}
protected void startCommon() {
mParent.removeCallbacks( this );
}
public void stop( boolean scrollIntoSlots ) {
mParent.removeCallbacks( this );
endFling( scrollIntoSlots );
}
public void startUsingDistance( int initialX, int distance ) {
if ( distance == 0 ) return;
startCommon();
mLastFlingX = initialX;
_startUsingDistance( mLastFlingX, distance );
mParent.post( this );
}
public void startUsingVelocity( int initialX, int initialVelocity ) {
if ( initialVelocity == 0 ) return;
startCommon();
mLastFlingX = initialX;
_startUsingVelocity( mLastFlingX, initialVelocity );
mParent.post( this );
}
protected void endFling( boolean scrollIntoSlots ) {
forceFinished( true );
mLastFlingX = 0;
if ( scrollIntoSlots ) {
mParent.scrollIntoSlots();
}
}
@Override
public void run() {
mShouldStopFling = false;
final boolean more = computeScrollOffset();
int x = getCurrX();
mParent.trackMotionScroll( x );
if ( more && !mShouldStopFling ) {
mLastFlingX = x;
mParent.post( this );
} else {
endFling( true );
}
}
public abstract boolean springBack( int startX, int startY, int minX, int maxX, int minY, int maxY );
protected abstract boolean computeScrollOffset();
protected abstract int getCurrX();
public abstract float getCurrVelocity();
protected abstract void forceFinished( boolean finished );
protected abstract void _startUsingVelocity( int initialX, int velocity );
protected abstract void _startUsingDistance( int initialX, int distance );
public abstract boolean isFinished();
}
下面是Fling8Runnable
package thepoor.com.testmywheel;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
class Fling8Runnable extends IFlingRunnable {
private Scroller mScroller;
public Fling8Runnable(FlingRunnableView parent, int animationDuration ) {
super( parent, animationDuration );
mScroller = new Scroller( ( (View) parent ).getContext(), new DecelerateInterpolator() );
}
@Override
public float getCurrVelocity() {
return mScroller.getCurrVelocity();
}
@Override
public boolean isFinished() {
return mScroller.isFinished();
}
@Override
protected void _startUsingVelocity( int initialX, int velocity ) {
mScroller.fling( initialX, 0, velocity, 0, mParent.getMinX(), mParent.getMaxX(), 0, Integer.MAX_VALUE );
}
@Override
protected void _startUsingDistance( int initialX, int distance ) {
mScroller.startScroll( initialX, 0, distance, 0, mAnimationDuration );
}
@Override
protected void forceFinished( boolean finished ) {
mScroller.forceFinished( finished );
}
@Override
protected boolean computeScrollOffset() {
return mScroller.computeScrollOffset();
}
@Override
protected int getCurrX() {
return mScroller.getCurrX();
}
@Override
public boolean springBack( int startX, int startY, int minX, int maxX, int minY, int maxY ) {
return false;
}
}