package com.jwipc.nodka_alarmpower; import java.util.List; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; public class LoopView extends View { public enum ACTION { CLICK, FLING, DAGGLE } Context context; Handler handler; private GestureDetector gestureDetector; OnItemSelectedListener onItemSelectedListener; ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture mFuture; Paint paintOuterText; Paint paintCenterText; Paint paintIndicator; List items; int textSize; int maxTextWidth = 50; int maxTextHeight = 50; int colorGray; int colorBlack; int colorLightGray; float lineSpacingMultiplier; boolean isLoop; int firstLineY; int secondLineY; int totalScrollY; int initPosition; private int selectedItem; int preCurrentIndex; int change; int itemsVisible; int measuredHeight; int measuredWidth; int paddingLeft = 10; int paddingRight = 0; int halfCircumference; int radius; private int mOffset = 0; private float previousY; long startTime = 0; final int WHAT_INVALIDATE_LOOP_VIEW = 1000; final int WHAT_SMOOTH_SCROLL = 2000; final int WHAT_ITEM_SELECTED = 3000; public LoopView(Context context) { super(context); initLoopView(context); } public LoopView(Context context, AttributeSet attributeset) { super(context, attributeset); initLoopView(context); } public LoopView(Context context, AttributeSet attributeset, int defStyleAttr) { super(context, attributeset, defStyleAttr); initLoopView(context); } private void initLoopView(Context context) { this.context = context; handler(); gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this)); gestureDetector.setIsLongpressEnabled(false); lineSpacingMultiplier = 1.6F; isLoop = true; itemsVisible = 5; textSize = 0; colorGray = 0xffafafaf; colorBlack = 0xff313131; colorLightGray = 0xff000000; totalScrollY = 0; initPosition = 0; initPaints(); setTextSize(16F); } private void initPaints() { paintOuterText = new Paint(); paintOuterText.setColor(colorGray); paintOuterText.setAntiAlias(true); paintOuterText.setTypeface(Typeface.MONOSPACE); paintOuterText.setTextSize(textSize); paintCenterText = new Paint(); paintCenterText.setColor(colorBlack); paintCenterText.setAntiAlias(true); paintCenterText.setTextScaleX(1.05F); paintCenterText.setTypeface(Typeface.MONOSPACE); paintCenterText.setTextSize(textSize); paintIndicator = new Paint(); paintIndicator.setColor(colorLightGray); paintIndicator.setAntiAlias(true); if (android.os.Build.VERSION.SDK_INT >= 11) { setLayerType(LAYER_TYPE_SOFTWARE, null); } } private void remeasure() { if (items == null) { return; } measureTextWidthHeight(); halfCircumference = (int) (maxTextHeight * lineSpacingMultiplier * (itemsVisible - 1)); measuredHeight = (int) ((halfCircumference * 2) / Math.PI); radius = (int) (halfCircumference / Math.PI); int extraRightWidth = (int) (maxTextWidth * 0.05) + 1; if (paddingRight<=extraRightWidth) { paddingRight = extraRightWidth; } measuredWidth = maxTextWidth + paddingLeft + paddingRight; firstLineY = (int) ((measuredHeight - maxTextHeight * lineSpacingMultiplier) / 2.0F); secondLineY = (int) ((measuredHeight + maxTextHeight * lineSpacingMultiplier) / 2.0F); preCurrentIndex = initPosition; } private void measureTextWidthHeight() { Rect rect = new Rect(); for (int i = 0; i < items.size(); i++) { String s1 = items.get(i); int textWidth = maxTextWidth; if (textWidth > maxTextWidth) { maxTextWidth = textWidth; } int textHeight = maxTextHeight; if (textHeight > maxTextHeight) { maxTextHeight = textHeight; } } } void smoothScroll(ACTION action) { cancelFuture(); if (action==ACTION.FLING||action==ACTION.DAGGLE) { float itemHeight = lineSpacingMultiplier * maxTextHeight; mOffset = (int) ((totalScrollY%itemHeight + itemHeight) % itemHeight); if ((float) mOffset > itemHeight / 2.0F) { mOffset = (int) (itemHeight - (float) mOffset); } else { mOffset = -mOffset; } } mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0, 10, TimeUnit.MILLISECONDS); } protected final void scrollBy(float velocityY) { cancelFuture(); // 修改这个值可以改变滑行速度 int velocityFling = 50; mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0, velocityFling, TimeUnit.MILLISECONDS); } public void cancelFuture() { if (mFuture!=null&&!mFuture.isCancelled()) { mFuture.cancel(true); mFuture = null; } } public final void setNotLoop() { isLoop = false; } public final void setTextSize(float size) { if (size > 0.0F) { textSize = (int) (context.getResources().getDisplayMetrics().density * size); paintOuterText.setTextSize(textSize); paintCenterText.setTextSize(textSize); } } public final void setInitPosition(int initPosition) { this.initPosition = initPosition; } public final void setListener(OnItemSelectedListener OnItemSelectedListener) { onItemSelectedListener = OnItemSelectedListener; } public final void setItems(List items) { this.items = items; remeasure(); invalidate(); } @Override public int getPaddingLeft() { return paddingLeft; } @Override public int getPaddingRight() { return paddingRight; } public void setViewPadding(int left, int top, int right, int bottom) { paddingLeft = left; paddingRight = right; } public final int getSelectedItem() { return selectedItem; } protected final void onItemSelected() { if (onItemSelectedListener != null) { postDelayed(new OnItemSelectedRunnable(this), 200L); } } @Override protected void onDraw(Canvas canvas) { if (items == null) { return; } change = (int) (totalScrollY / (lineSpacingMultiplier * maxTextHeight)); preCurrentIndex = initPosition + change % items.size(); if (!isLoop) { if (preCurrentIndex < 0) { preCurrentIndex = 0; } if (preCurrentIndex > items.size() - 1) { preCurrentIndex = items.size() - 1; } } else { if (preCurrentIndex < 0) { preCurrentIndex = items.size() + preCurrentIndex; } if (preCurrentIndex > items.size() - 1) { preCurrentIndex = preCurrentIndex - items.size(); } } String as[] = new String[itemsVisible]; int k1 = 0; while (k1 < itemsVisible) { int l1 = preCurrentIndex - (itemsVisible / 2 - k1); if (isLoop) { if (l1 < 0) { l1 = l1 + items.size(); } if (l1 > items.size() - 1) { l1 = l1 - items.size(); } as[k1] = items.get(l1); } else if (l1 < 0) { as[k1] = ""; } else if (l1 > items.size() - 1) { as[k1] = ""; } else { as[k1] = items.get(l1); } k1++; } canvas.drawLine(0.0F, firstLineY, measuredWidth, firstLineY, paintIndicator); canvas.drawLine(0.0F, secondLineY, measuredWidth, secondLineY, paintIndicator); int m1 = paddingLeft; int j1 = 0; int j2 = (int) (totalScrollY % (lineSpacingMultiplier * maxTextHeight)); while (j1 < itemsVisible) { canvas.save(); float itemHeight = maxTextHeight * lineSpacingMultiplier; double radian = ((itemHeight * j1 - j2) * Math.PI) / halfCircumference; float angle = (float) (90D - (radian / Math.PI) * 180D); if (angle >= 90F || angle <= -90F) { canvas.restore(); } else { int translateY = (int) (radius - Math.cos(radian) * radius - (Math.sin(radian) * maxTextHeight) / 2D); canvas.translate(0.0F, translateY); canvas.scale(1.0F, (float) Math.sin(radian)); if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) { canvas.save(); canvas.clipRect(0, 0, measuredWidth, firstLineY - translateY); canvas.drawText(as[j1], m1, maxTextHeight, paintOuterText); canvas.restore(); canvas.save(); canvas.clipRect(0, firstLineY - translateY, measuredWidth, (int) (itemHeight)); canvas.drawText(as[j1], m1, maxTextHeight, paintCenterText); canvas.restore(); } else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) { canvas.save(); canvas.clipRect(0, 0, measuredWidth, secondLineY - translateY); canvas.drawText(as[j1], m1, maxTextHeight, paintCenterText); canvas.restore(); canvas.save(); canvas.clipRect(0, secondLineY - translateY, measuredWidth, (int) (itemHeight)); canvas.drawText(as[j1], m1, maxTextHeight, paintOuterText); canvas.restore(); } else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) { canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight)); canvas.drawText(as[j1], m1, maxTextHeight-10, paintCenterText); selectedItem = items.indexOf(as[j1]); } else { canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight)); canvas.drawText(as[j1], m1, maxTextHeight, paintOuterText); } canvas.restore(); } j1++; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { remeasure(); setMeasuredDimension(measuredWidth, measuredHeight); } @Override public boolean onTouchEvent(MotionEvent event) { boolean eventConsumed = gestureDetector.onTouchEvent(event); float itemHeight = lineSpacingMultiplier * maxTextHeight; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startTime = System.currentTimeMillis(); cancelFuture(); previousY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: float dy = previousY - event.getRawY(); previousY = event.getRawY(); totalScrollY = (int) (totalScrollY + dy); if (!isLoop) { float top = -initPosition * itemHeight; float bottom = (items.size() - 1 - initPosition) * itemHeight; if (totalScrollY < top) { totalScrollY = (int) top; } else if (totalScrollY > bottom) { totalScrollY = (int) bottom; } } break; case MotionEvent.ACTION_UP: default: if (!eventConsumed) { float y = event.getY(); double l = Math.acos((radius - y) / radius) * radius; int circlePosition = (int) ((l + itemHeight / 2) / itemHeight); float extraOffset = (totalScrollY % itemHeight + itemHeight) % itemHeight; mOffset = (int) ((circlePosition - itemsVisible / 2) * itemHeight - extraOffset); if ((System.currentTimeMillis() - startTime) > 120) { smoothScroll(ACTION.DAGGLE); } else { smoothScroll(ACTION.CLICK); } } break; } invalidate(); return true; } public void handler() { handler = new Handler() { @Override public final void handleMessage(Message msg) { switch (msg.what) { case WHAT_INVALIDATE_LOOP_VIEW: invalidate(); break; case WHAT_SMOOTH_SCROLL: smoothScroll(LoopView.ACTION.FLING); break; case WHAT_ITEM_SELECTED: onItemSelected(); break; } } }; } class LoopViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener { final LoopView loopView; LoopViewGestureListener(LoopView loopview) { loopView = loopview; } @Override public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { loopView.scrollBy(velocityY); return true; } } class InertiaTimerTask extends TimerTask { float a; final float velocityY; final LoopView loopView; InertiaTimerTask(LoopView loopview, float velocityY) { super(); loopView = loopview; this.velocityY = velocityY; a = Integer.MAX_VALUE; } @Override public final void run() { if (a == Integer.MAX_VALUE) { if (Math.abs(velocityY) > 2000F) { if (velocityY > 0.0F) { a = 2000F; } else { a = -2000F; } } else { a = velocityY; } } if (Math.abs(a) >= 0.0F && Math.abs(a) <= 20F) { loopView.cancelFuture(); loopView.handler.sendEmptyMessage(loopView.WHAT_SMOOTH_SCROLL); return; } int i = (int) ((a * 10F) / 1000F); LoopView loopview = loopView; loopview.totalScrollY = loopview.totalScrollY - i; if (!loopView.isLoop) { float itemHeight = loopView.lineSpacingMultiplier * loopView.maxTextHeight; if (loopView.totalScrollY <= (int) ((float) (-loopView.initPosition) * itemHeight)) { a = 40F; loopView.totalScrollY = (int) ((float) (-loopView.initPosition) * itemHeight); } else if (loopView.totalScrollY >= (int) ((float) (loopView.items.size() - 1 - loopView.initPosition) * itemHeight)) { loopView.totalScrollY = (int) ((float) (loopView.items.size() - 1 - loopView.initPosition) * itemHeight); a = -40F; } } if (a < 0.0F) { a = a + 20F; } else { a = a - 20F; } loopView.handler.sendEmptyMessage(loopView.WHAT_INVALIDATE_LOOP_VIEW); } } public interface OnItemSelectedListener { void onItemSelected(int index); } class OnItemSelectedRunnable implements Runnable { final LoopView loopView; OnItemSelectedRunnable(LoopView loopview) { loopView = loopview; } @Override public final void run() { loopView.onItemSelectedListener.onItemSelected(loopView.getSelectedItem()); } } class SmoothScrollTimerTask extends TimerTask { int realTotalOffset; int realOffset; int offset; final LoopView loopView; SmoothScrollTimerTask(LoopView loopview, int offset) { this.loopView = loopview; this.offset = offset; realTotalOffset = Integer.MAX_VALUE; realOffset = 0; } @Override public final void run() { if (realTotalOffset == Integer.MAX_VALUE) { realTotalOffset = offset; } realOffset = (int) ((float) realTotalOffset * 0.1F); if (realOffset == 0) { if (realTotalOffset < 0) { realOffset = -1; } else { realOffset = 1; } } if (Math.abs(realTotalOffset) <= 0) { loopView.cancelFuture(); loopView.handler.sendEmptyMessage(loopView.WHAT_ITEM_SELECTED); } else { loopView.totalScrollY = loopView.totalScrollY + realOffset; loopView.handler.sendEmptyMessage(loopView.WHAT_INVALIDATE_LOOP_VIEW); realTotalOffset = realTotalOffset - realOffset; } } } }