很多APP.如今会用到横向刻度尺,或是纵向刻度尺....比如在个人资料填入.或是减肥APP里面...随处可见
今天我带来一个
先发,我录制的动态GIF效果图
放代码
主Activity
public class MainActivity extends AppCompatActivity implements HorizontalPicker.OnItemSelected, HorizontalPicker.OnItemClicked {
HorizontalPicker pick;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//数据类型(0 卡路里,100-2000, 1:步数 2000-20000 2;公里:1-20
//默认选中数
pick =(HorizontalPicker)findViewById(R.id.picker);
pick.setDataType(2,"10");
pick.setOnItemClickedListener(this);
pick.setOnItemSelectedListener(this);
}
@Override
public void onItemClicked(int index) {
Toast.makeText(this, index+"", 1).show();
}
@Override
public void onItemSelected(String item) {
Toast.makeText(this, item, 1).show();
}
}
xml布局
自定义
HorizontalPicker
/*
* Copyright 2014 Blaž Šolar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.yangbin.pickm;
import android.animation.ArgbEvaluator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.text.TextDirectionHeuristicCompat;
import android.support.v4.text.TextDirectionHeuristicsCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.text.BoringLayout;
import android.text.Layout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.EdgeEffect;
import android.widget.OverScroller;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Blaž Šolar on 24/01/14.
*/
public class HorizontalPicker extends View {
public static final String TAG = "HorizontalTimePicker";
/**
* The coefficient by which to adjust (divide) the max fling velocity.
*/
private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 4;
/**
* The the duration for adjusting the selector wheel.
*/
private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
/**
* Determines speed during touch scrolling.
*/
private VelocityTracker mVelocityTracker;
/**
* @see ViewConfiguration#getScaledMinimumFlingVelocity()
*/
private int mMinimumFlingVelocity;
/**
* @see ViewConfiguration#getScaledMaximumFlingVelocity()
*/
private int maximumFlingVelocity;
private final int overscrollDistance;
private int touchSlop;
private CharSequence[] values;
private BoringLayout[] layouts;
private TextPaint textPaint;
private BoringLayout.Metrics boringMetrics;
private TextUtils.TruncateAt ellipsize;
private int itemWidth;
private RectF itemClipBounds;
private RectF itemClipBoundsOffset;
private float lastDownEventX;
private OverScroller flingScrollerX;
private OverScroller adjustScrollerX;
private int previousScrollerX;
private boolean scrollingX;
private int pressedItem = -1;
private ColorStateList textColor;
private OnItemSelected onItemSelected;
private OnItemClicked onItemClicked;
private int selectedItem;
private EdgeEffect leftEdgeEffect;
private EdgeEffect rightEdgeEffect;
private Marquee marquee;
private int marqueeRepeatLimit = 3;
private float dividerSize = 0;
private int sideItems = 1;
private TextDirectionHeuristicCompat textDir;
private final PickerTouchHelper touchHelper;
private Paint paint;
private int mWidth;
private int mHeight;
private String[] arrays;
private int mDataType = 0;
public void setDataType(int type, String value) {
mDataType = type;
setValues(getData(mDataType));
setSelectedItem(value);
invalidate();
}
public int getDataType() {
return mDataType;
}
private void init() {
paint = new Paint();
paint.setColor(Color.GRAY);
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
}
public HorizontalPicker(Context context) {
this(context, null);
}
public HorizontalPicker(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.horizontalPickerStyle);
}
public HorizontalPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
// create the selector wheel paint
TextPaint paint = new TextPaint();
paint.setAntiAlias(true);
textPaint = paint;
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.HorizontalPicker,
defStyle, 0
);
int ellipsize = 3; // END default value
int sideItems = this.sideItems;
try {
textColor = a.getColorStateList(R.styleable.HorizontalPicker_android_textColor);
if (textColor == null) {
textColor = ColorStateList.valueOf(0xFF000000);
}
ellipsize = a.getInt(R.styleable.HorizontalPicker_android_ellipsize, ellipsize);
marqueeRepeatLimit = a.getInt(R.styleable.HorizontalPicker_android_marqueeRepeatLimit, marqueeRepeatLimit);
dividerSize = a.getDimension(R.styleable.HorizontalPicker_dividerSize, dividerSize);
sideItems = a.getInt(R.styleable.HorizontalPicker_sideItems, sideItems);
float textSize = a.getDimension(R.styleable.HorizontalPicker_android_textSize, -1);
if (textSize > -1) {
setTextSize(textSize);
}
} finally {
a.recycle();
}
switch (ellipsize) {
case 1:
setEllipsize(TextUtils.TruncateAt.START);
break;
case 2:
setEllipsize(TextUtils.TruncateAt.MIDDLE);
break;
case 3:
setEllipsize(TextUtils.TruncateAt.END);
break;
case 4:
setEllipsize(TextUtils.TruncateAt.MARQUEE);
break;
}
Paint.FontMetricsInt fontMetricsInt = textPaint.getFontMetricsInt();
boringMetrics = new BoringLayout.Metrics();
boringMetrics.ascent = fontMetricsInt.ascent;
boringMetrics.bottom = fontMetricsInt.bottom;
boringMetrics.descent = fontMetricsInt.descent;
boringMetrics.leading = fontMetricsInt.leading;
boringMetrics.top = fontMetricsInt.top;
boringMetrics.width = itemWidth;
setWillNotDraw(false);
flingScrollerX = new OverScroller(context);
adjustScrollerX = new OverScroller(context, new DecelerateInterpolator(2.5f));
// initialize constants
ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
maximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
/ SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
overscrollDistance = configuration.getScaledOverscrollDistance();
previousScrollerX = Integer.MIN_VALUE;
//********添加数据**********
setValues(getData(getDataType()));
setSideItems(sideItems);
touchHelper = new PickerTouchHelper(this);
ViewCompat.setAccessibilityDelegate(this, touchHelper);
}
/**
* 自定义数据
*
* @return
*/
private String[] getData(int type) {
List list = new ArrayList<>();
if (type == 1) {
arrays = new String[19];
for (int i = 2000; i <= 20000; i += 1000) {
list.add(i + "");
}
for (int k = 0; k < 19; k++) {
arrays[k] = list.get(k);
}
} else {
if (type == 0) {
arrays = new String[20];
for (int i = 100; i <= 2000; i += 100) {
list.add(i + "");
}
} else if (type == 2) {
arrays = new String[20];
for (int i = 1; i <= 20; i += 1) {
list.add(i + "");
}
}
for (int k = 0; k < 20; k++) {
arrays[k] = list.get(k);
}
}
return arrays;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int height;
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
int heightText = (int) (Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent));
heightText += getPaddingTop() + getPaddingBottom();
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(heightSize, heightText);
} else {
height = heightText;
}
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = getWidth();
mHeight = getHeight();
int saveCount = canvas.getSaveCount();
canvas.save();
int selectedItem = this.selectedItem;
float itemWithPadding = itemWidth + dividerSize;
// translate horizontal to center
canvas.translate(itemWithPadding * sideItems, 0);
if (values != null) {
for (int i = 0; i < values.length; i++) {
// set text color for item
textPaint.setColor(getTextColor(i));
// get text layout
BoringLayout layout = layouts[i];
int saveCountHeight = canvas.getSaveCount();
canvas.save();
float x = 0;
float lineWidth = layout.getLineWidth(0);
if (lineWidth > itemWidth) {
if (isRtl(values[i])) {
x += (lineWidth - itemWidth) / 2;
} else {
x -= (lineWidth - itemWidth) / 2;
}
}
if (marquee != null && i == selectedItem) {
x += marquee.getScroll();
}
// translate vertically to center
canvas.translate(-x, (canvas.getHeight() - layout.getHeight()) / 2);
RectF clipBounds;
if (x == 0) {
clipBounds = itemClipBounds;
} else {
clipBounds = itemClipBoundsOffset;
clipBounds.set(itemClipBounds);
clipBounds.offset(x, 0);
}
canvas.clipRect(clipBounds);
layout.draw(canvas);
if (marquee != null && i == selectedItem && marquee.shouldDrawGhost()) {
canvas.translate(marquee.getGhostOffset(), 0);
layout.draw(canvas);
}
// restore vertical translation
canvas.restoreToCount(saveCountHeight);
// 画刻度线
paint.setColor(getTextColor(i));
canvas.drawLine(itemWidth / 2, mHeight - 20, itemWidth / 2, mHeight - 45, paint);
// translate horizontal for 1 item
canvas.translate(itemWithPadding, 0);
}
}
// restore horizontal translation
canvas.restoreToCount(saveCount);
drawEdgeEffect(canvas, leftEdgeEffect, 270);
drawEdgeEffect(canvas, rightEdgeEffect, 90);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
textDir = getTextDirectionHeuristic();
}
/**
* TODO cache values
*
* @param text
* @return
*/
private boolean isRtl(CharSequence text) {
if (textDir == null) {
textDir = getTextDirectionHeuristic();
}
return textDir.isRtl(text, 0, text.length());
}
private TextDirectionHeuristicCompat getTextDirectionHeuristic() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
return TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR;
} else {
// Always need to resolve layout direction first
final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
switch (getTextDirection()) {
default:
case TEXT_DIRECTION_FIRST_STRONG:
return (defaultIsRtl ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL :
TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR);
case TEXT_DIRECTION_ANY_RTL:
return TextDirectionHeuristicsCompat.ANYRTL_LTR;
case TEXT_DIRECTION_LTR:
return TextDirectionHeuristicsCompat.LTR;
case TEXT_DIRECTION_RTL:
return TextDirectionHeuristicsCompat.RTL;
case TEXT_DIRECTION_LOCALE:
return TextDirectionHeuristicsCompat.LOCALE;
}
}
}
private void remakeLayout() {
if (layouts != null && layouts.length > 0 && getWidth() > 0) {
for (int i = 0; i < layouts.length; i++) {
layouts[i].replaceOrMake(values[i], textPaint, itemWidth,
Layout.Alignment.ALIGN_CENTER, 1f, 1f, boringMetrics, false, ellipsize,
itemWidth);
}
}
}
private void drawEdgeEffect(Canvas canvas, EdgeEffect edgeEffect, int degrees) {
if (canvas == null || edgeEffect == null || (degrees != 90 && degrees != 270)) {
return;
}
if (!edgeEffect.isFinished()) {
final int restoreCount = canvas.getSaveCount();
final int width = getWidth();
final int height = getHeight();
canvas.rotate(degrees);
if (degrees == 270) {
canvas.translate(-height, Math.max(0, getScrollX()));
} else { // 90
canvas.translate(0, -(Math.max(getScrollRange(), getScaleX()) + width));
}
edgeEffect.setSize(height, width);
if (edgeEffect.draw(canvas)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postInvalidateOnAnimation();
} else {
postInvalidate();
}
}
canvas.restoreToCount(restoreCount);
}
}
/**
* Calculates text color for specified item based on its position and state.
*
* @param item Index of item to get text color for
* @return Item text color
*/
private int getTextColor(int item) {
int scrollX = getScrollX();
// set color of text
int color = textColor.getDefaultColor();
int itemWithPadding = (int) (itemWidth + dividerSize);
if (scrollX > itemWithPadding * item - itemWithPadding / 2 &&
scrollX < itemWithPadding * (item + 1) - itemWithPadding / 2) {
int position = scrollX - itemWithPadding / 2;
color = getColor(position, item);
} else if (item == pressedItem) {
color = textColor.getColorForState(new int[]{android.R.attr.state_pressed}, color);
}
return color;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
calculateItemSize(w, h);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_MOVE:
float currentMoveX = event.getX();
int deltaMoveX = (int) (lastDownEventX - currentMoveX);
if (scrollingX ||
(Math.abs(deltaMoveX) > touchSlop) && values != null && values.length > 0) {
if (!scrollingX) {
deltaMoveX = 0;
pressedItem = -1;
scrollingX = true;
getParent().requestDisallowInterceptTouchEvent(true);
stopMarqueeIfNeeded();
}
final int range = getScrollRange();
if (overScrollBy(deltaMoveX, 0, getScrollX(), 0, range, 0,
overscrollDistance, 0, true)) {
mVelocityTracker.clear();
}
final float pulledToX = getScrollX() + deltaMoveX;
if (pulledToX < 0) {
leftEdgeEffect.onPull((float) deltaMoveX / getWidth());
if (!rightEdgeEffect.isFinished()) {
rightEdgeEffect.onRelease();
}
} else if (pulledToX > range) {
rightEdgeEffect.onPull((float) deltaMoveX / getWidth());
if (!leftEdgeEffect.isFinished()) {
leftEdgeEffect.onRelease();
}
}
lastDownEventX = currentMoveX;
invalidate();
}
break;
case MotionEvent.ACTION_DOWN:
if (!adjustScrollerX.isFinished()) {
adjustScrollerX.forceFinished(true);
} else if (!flingScrollerX.isFinished()) {
flingScrollerX.forceFinished(true);
} else {
scrollingX = false;
}
lastDownEventX = event.getX();
if (!scrollingX) {
pressedItem = getPositionFromTouch(event.getX());
}
invalidate();
break;
case MotionEvent.ACTION_UP:
VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, maximumFlingVelocity);
int initialVelocityX = (int) velocityTracker.getXVelocity();
if (scrollingX && Math.abs(initialVelocityX) > mMinimumFlingVelocity) {
flingX(initialVelocityX);
} else if (values != null) {
float positionX = event.getX();
if (!scrollingX) {
int itemPos = getPositionOnScreen(positionX);
int relativePos = itemPos - sideItems;
if (relativePos == 0) {
selectItem();
} else {
smoothScrollBy(relativePos);
}
} else if (scrollingX) {
finishScrolling();
}
}
mVelocityTracker.recycle();
mVelocityTracker = null;
if (leftEdgeEffect != null) {
leftEdgeEffect.onRelease();
rightEdgeEffect.onRelease();
}
case MotionEvent.ACTION_CANCEL:
pressedItem = -1;
invalidate();
if (leftEdgeEffect != null) {
leftEdgeEffect.onRelease();
rightEdgeEffect.onRelease();
}
break;
}
return true;
}
private void selectItem() {
// post to the UI Thread to avoid potential interference with the OpenGL Thread
if (onItemClicked != null) {
post(new Runnable() {
@Override
public void run() {
onItemClicked.onItemClicked(getSelectedItem());
}
});
}
adjustToNearestItemX();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (!isEnabled()) {
return super.onKeyDown(keyCode, event);
}
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
selectItem();
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
smoothScrollBy(-1);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
smoothScrollBy(1);
return true;
default:
return super.onKeyDown(keyCode, event);
}
}
@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
if (touchHelper.dispatchHoverEvent(event)) {
return true;
}
return super.dispatchHoverEvent(event);
}
@Override
public void computeScroll() {
computeScrollX();
}
@Override
public void getFocusedRect(Rect r) {
super.getFocusedRect(r); // TODO this should only be current item
}
public void setOnItemSelectedListener(OnItemSelected onItemSelected) {
this.onItemSelected = onItemSelected;
}
public void setOnItemClickedListener(OnItemClicked onItemClicked) {
this.onItemClicked = onItemClicked;
}
public int getSelectedItem() {
int x = getScrollX();
return getPositionFromCoordinates(x);
}
public void setSelectedItem(int index) {
selectedItem = index;
scrollToItem(index);
}
/**
* 设置选中项
*
* @param item
*/
public void setSelectedItem(String item) {
selectedItem = getIndex(item);
scrollToItem(selectedItem);
}
/**
* 根据数据返回相应的下标
*
* @param item
* @return
*/
private int getIndex(String item) {
for (int i = 0; i < arrays.length - 1; i++) {
if (arrays[i].equals(item))
return i;
}
return 0;
}
public int getMarqueeRepeatLimit() {
return marqueeRepeatLimit;
}
public void setMarqueeRepeatLimit(int marqueeRepeatLimit) {
this.marqueeRepeatLimit = marqueeRepeatLimit;
}
/**
* @return Number of items on each side of current item.
*/
public int getSideItems() {
return sideItems;
}
public void setSideItems(int sideItems) {
if (this.sideItems < 0) {
throw new IllegalArgumentException("Number of items on each side must be grater or equal to 0.");
} else if (this.sideItems != sideItems) {
this.sideItems = sideItems;
calculateItemSize(getWidth(), getHeight());
}
}
/**
* @return
*/
public CharSequence[] getValues() {
return values;
}
/**
* Sets values to choose from
*
* @param values New values to choose from
*/
public void setValues(CharSequence[] values) {
if (this.values != values) {
this.values = values;
if (this.values != null) {
layouts = new BoringLayout[this.values.length];
for (int i = 0; i < layouts.length; i++) {
layouts[i] = new BoringLayout(this.values[i], textPaint, itemWidth, Layout.Alignment.ALIGN_CENTER,
1f, 1f, boringMetrics, false, ellipsize, itemWidth);
}
} else {
layouts = new BoringLayout[0];
}
// start marque only if has already been measured
if (getWidth() > 0) {
startMarqueeIfNeeded();
}
requestLayout();
invalidate();
}
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setSelectedItem(ss.mSelItem);
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.mSelItem = selectedItem;
return savedState;
}
@Override
public void setOverScrollMode(int overScrollMode) {
if (overScrollMode != OVER_SCROLL_NEVER) {
Context context = getContext();
leftEdgeEffect = new EdgeEffect(context);
rightEdgeEffect = new EdgeEffect(context);
} else {
leftEdgeEffect = null;
rightEdgeEffect = null;
}
super.setOverScrollMode(overScrollMode);
}
public TextUtils.TruncateAt getEllipsize() {
return ellipsize;
}
public void setEllipsize(TextUtils.TruncateAt ellipsize) {
if (this.ellipsize != ellipsize) {
this.ellipsize = ellipsize;
remakeLayout();
invalidate();
}
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.scrollTo(scrollX, scrollY);
if (!flingScrollerX.isFinished() && clampedX) {
flingScrollerX.springBack(scrollX, scrollY, 0, getScrollRange(), 0, 0);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged(); //TODO
}
private int getPositionFromTouch(float x) {
return getPositionFromCoordinates((int) (getScrollX() - (itemWidth + dividerSize) * (sideItems + .5f) + x));
}
private void computeScrollX() {
OverScroller scroller = flingScrollerX;
if (scroller.isFinished()) {
scroller = adjustScrollerX;
if (scroller.isFinished()) {
return;
}
}
if (scroller.computeScrollOffset()) {
int currentScrollerX = scroller.getCurrX();
if (previousScrollerX == Integer.MIN_VALUE) {
previousScrollerX = scroller.getStartX();
}
int range = getScrollRange();
if (previousScrollerX >= 0 && currentScrollerX < 0) {
leftEdgeEffect.onAbsorb((int) scroller.getCurrVelocity());
} else if (previousScrollerX <= range && currentScrollerX > range) {
rightEdgeEffect.onAbsorb((int) scroller.getCurrVelocity());
}
overScrollBy(currentScrollerX - previousScrollerX, 0, previousScrollerX, getScrollY(),
getScrollRange(), 0, overscrollDistance, 0, false);
previousScrollerX = currentScrollerX;
if (scroller.isFinished()) {
onScrollerFinishedX(scroller);
}
postInvalidate();
// postInvalidateOnAnimation(); // TODO
}
}
private void flingX(int velocityX) {
previousScrollerX = Integer.MIN_VALUE;
flingScrollerX.fling(getScrollX(), getScrollY(), -velocityX, 0, 0,
(int) (itemWidth + dividerSize) * (values.length - 1), 0, 0, getWidth() / 2, 0);
invalidate();
}
private void adjustToNearestItemX() {
int x = getScrollX();
int item = Math.round(x / (itemWidth + dividerSize * 1f));
if (item < 0) {
item = 0;
} else if (item > values.length) {
item = values.length;
}
selectedItem = item;
int itemX = (itemWidth + (int) dividerSize) * item;
int deltaX = itemX - x;
previousScrollerX = Integer.MIN_VALUE;
adjustScrollerX.startScroll(x, 0, deltaX, 0, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
invalidate();
}
private void calculateItemSize(int w, int h) {
int items = sideItems * 2 + 1;
int totalPadding = ((int) dividerSize * (items - 1));
itemWidth = (w - totalPadding) / items;
itemClipBounds = new RectF(0, 0, itemWidth, h);
itemClipBoundsOffset = new RectF(itemClipBounds);
scrollToItem(selectedItem);
remakeLayout();
startMarqueeIfNeeded();
}
private void onScrollerFinishedX(OverScroller scroller) {
if (scroller == flingScrollerX) {
finishScrolling();
}
}
private void finishScrolling() {
adjustToNearestItemX();
scrollingX = false;
startMarqueeIfNeeded();
// post to the UI Thread to avoid potential interference with the OpenGL Thread
if (onItemSelected != null) {
post(new Runnable() {
@Override
public void run() {
// onItemSelected.onItemSelected(getPositionFromCoordinates(getScrollX()));
onItemSelected.onItemSelected(arrays[getPositionFromCoordinates(getScrollX())]);
}
});
}
}
private void startMarqueeIfNeeded() {
stopMarqueeIfNeeded();
int item = getSelectedItem();
if (layouts != null && layouts.length > item) {
Layout layout = layouts[item];
if (ellipsize == TextUtils.TruncateAt.MARQUEE
&& itemWidth < layout.getLineWidth(0)) {
marquee = new Marquee(this, layout, isRtl(values[item]));
marquee.start(marqueeRepeatLimit);
}
}
}
private void stopMarqueeIfNeeded() {
if (marquee != null) {
marquee.stop();
marquee = null;
}
}
private int getPositionOnScreen(float x) {
return (int) (x / (itemWidth + dividerSize));
}
private void smoothScrollBy(int i) {
int deltaMoveX = (itemWidth + (int) dividerSize) * i;
deltaMoveX = getRelativeInBound(deltaMoveX);
previousScrollerX = Integer.MIN_VALUE;
flingScrollerX.startScroll(getScrollX(), 0, deltaMoveX, 0);
stopMarqueeIfNeeded();
invalidate();
}
/**
* Calculates color for specific position on time picker
* 修改选中颜色
*
* @param scrollX
* @return
*/
private int getColor(int scrollX, int position) {
int itemWithPadding = (int) (itemWidth + dividerSize);
float proportion = Math.abs(((1f * scrollX % itemWithPadding) / 2) / (itemWithPadding / 2f));
if (proportion > .5) {
proportion = (proportion - .5f);
} else {
proportion = .5f - proportion;
}
proportion *= 2;
int defaultColor;
int selectedColor;
if (pressedItem == position) {
defaultColor = textColor.getColorForState(new int[]{android.R.attr.state_pressed}, textColor.getDefaultColor());
// selectedColor = textColor.getColorForState(new int[]{android.R.attr.state_pressed, android.R.attr.state_selected}, defaultColor);
selectedColor = Color.RED;
} else {
defaultColor = textColor.getDefaultColor();
// selectedColor = textColor.getColorForState(new int[]{android.R.attr.state_selected}, defaultColor);
selectedColor = Color.RED;
}
return (Integer) new ArgbEvaluator().evaluate(proportion, selectedColor, defaultColor);
}
/**
* Sets text size for items
*
* @param size New item text size in px.
*/
private void setTextSize(float size) {
if (size != textPaint.getTextSize()) {
textPaint.setTextSize(size);
requestLayout();
invalidate();
}
}
/**
* Calculates item from x coordinate position.
*
* @param x Scroll position to calculate.
* @return Selected item from scrolling position in {param x}
*/
private int getPositionFromCoordinates(int x) {
return Math.round(x / (itemWidth + dividerSize));
}
/**
* Scrolls to specified item.
*
* @param index Index of an item to scroll to
*/
private void scrollToItem(int index) {
scrollTo((itemWidth + (int) dividerSize) * index, 0);
// invalidate() not needed because scrollTo() already invalidates the view
}
/**
* Calculates relative horizontal scroll position to be within our scroll bounds.
*
* @param x Relative scroll position to calculate
* @return Current scroll position + {param x} if is within our scroll bounds, otherwise it
* will return min/max scroll position.
*/
private int getRelativeInBound(int x) {
int scrollX = getScrollX();
return getInBoundsX(scrollX + x) - scrollX;
}
/**
* Calculates x scroll position that is still in range of view scroller
*
* @param x Scroll position to calculate.
* @return {param x} if is within bounds of over scroller, otherwise it will return min/max
* value of scoll position.
*/
private int getInBoundsX(int x) {
if (x < 0) {
x = 0;
} else if (x > ((itemWidth + (int) dividerSize) * (values.length - 1))) {
x = ((itemWidth + (int) dividerSize) * (values.length - 1));
}
return x;
}
private int getScrollRange() {
int scrollRange = 0;
if (values != null && values.length != 0) {
scrollRange = Math.max(0, ((itemWidth + (int) dividerSize) * (values.length - 1)));
}
return scrollRange;
}
public interface OnItemSelected {
// public void onItemSelected(int index);
public void onItemSelected(String item);
}
public interface OnItemClicked {
public void onItemClicked(int index);
}
private static final class Marquee extends Handler {
// TODO: Add an option to configure this
private static final float MARQUEE_DELTA_MAX = 0.07f;
private static final int MARQUEE_DELAY = 1200;
private static final int MARQUEE_RESTART_DELAY = 1200;
private static final int MARQUEE_RESOLUTION = 1000 / 30;
private static final int MARQUEE_PIXELS_PER_SECOND = 30;
private static final byte MARQUEE_STOPPED = 0x0;
private static final byte MARQUEE_STARTING = 0x1;
private static final byte MARQUEE_RUNNING = 0x2;
private static final int MESSAGE_START = 0x1;
private static final int MESSAGE_TICK = 0x2;
private static final int MESSAGE_RESTART = 0x3;
private final WeakReference mView;
private final WeakReference mLayout;
private byte mStatus = MARQUEE_STOPPED;
private final float mScrollUnit;
private float mMaxScroll;
private float mMaxFadeScroll;
private float mGhostStart;
private float mGhostOffset;
private float mFadeStop;
private int mRepeatLimit;
private float mScroll;
private boolean mRtl;
Marquee(HorizontalPicker v, Layout l, boolean rtl) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
float scrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
if (rtl) {
mScrollUnit = -scrollUnit;
} else {
mScrollUnit = scrollUnit;
}
mView = new WeakReference(v);
mLayout = new WeakReference(l);
mRtl = rtl;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_START:
mStatus = MARQUEE_RUNNING;
tick();
break;
case MESSAGE_TICK:
tick();
break;
case MESSAGE_RESTART:
if (mStatus == MARQUEE_RUNNING) {
if (mRepeatLimit >= 0) {
mRepeatLimit--;
}
start(mRepeatLimit);
}
break;
}
}
void tick() {
if (mStatus != MARQUEE_RUNNING) {
return;
}
removeMessages(MESSAGE_TICK);
final HorizontalPicker view = mView.get();
final Layout layout = mLayout.get();
if (view != null && layout != null && (view.isFocused() || view.isSelected())) {
mScroll += mScrollUnit;
if (Math.abs(mScroll) > mMaxScroll) {
mScroll = mMaxScroll;
if (mRtl) {
mScroll *= -1;
}
sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
} else {
sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
}
view.invalidate();
}
}
void stop() {
mStatus = MARQUEE_STOPPED;
removeMessages(MESSAGE_START);
removeMessages(MESSAGE_RESTART);
removeMessages(MESSAGE_TICK);
resetScroll();
}
private void resetScroll() {
mScroll = 0.0f;
final HorizontalPicker view = mView.get();
if (view != null) view.invalidate();
}
void start(int repeatLimit) {
if (repeatLimit == 0) {
stop();
return;
}
mRepeatLimit = repeatLimit;
final HorizontalPicker view = mView.get();
final Layout layout = mLayout.get();
if (view != null && layout != null) {
mStatus = MARQUEE_STARTING;
mScroll = 0.0f;
final int textWidth = view.itemWidth;
final float lineWidth = layout.getLineWidth(0);
final float gap = textWidth / 3.0f;
mGhostStart = lineWidth - textWidth + gap;
mMaxScroll = mGhostStart + textWidth;
mGhostOffset = lineWidth + gap;
mFadeStop = lineWidth + textWidth / 6.0f;
mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
if (mRtl) {
mGhostOffset *= -1;
}
view.invalidate();
sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
}
}
float getGhostOffset() {
return mGhostOffset;
}
float getScroll() {
return mScroll;
}
float getMaxFadeScroll() {
return mMaxFadeScroll;
}
boolean shouldDrawLeftFade() {
return mScroll <= mFadeStop;
}
boolean shouldDrawGhost() {
return mStatus == MARQUEE_RUNNING && Math.abs(mScroll) > mGhostStart;
}
boolean isRunning() {
return mStatus == MARQUEE_RUNNING;
}
boolean isStopped() {
return mStatus == MARQUEE_STOPPED;
}
}
public static class SavedState extends BaseSavedState {
private int mSelItem;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
mSelItem = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mSelItem);
}
@Override
public String toString() {
return "HorizontalPicker.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " selItem=" + mSelItem
+ "}";
}
@SuppressWarnings("hiding")
public static final Creator CREATOR
= new Creator() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
private static class PickerTouchHelper extends ExploreByTouchHelper {
private HorizontalPicker mPicker;
public PickerTouchHelper(HorizontalPicker picker) {
super(picker);
mPicker = picker;
}
@Override
protected int getVirtualViewAt(float x, float y) {
float itemWidth = mPicker.itemWidth + mPicker.dividerSize;
float position = mPicker.getScrollX() + x - itemWidth * mPicker.sideItems;
float item = position / itemWidth;
if (item < 0 || item > mPicker.values.length) {
return INVALID_ID;
}
return (int) item;
}
@Override
protected void getVisibleVirtualViews(List virtualViewIds) {
float itemWidth = mPicker.itemWidth + mPicker.dividerSize;
float position = mPicker.getScrollX() - itemWidth * mPicker.sideItems;
int first = (int) (position / itemWidth);
int items = mPicker.sideItems * 2 + 1;
if (position % itemWidth != 0) { // if start next item is starting to appear on screen
items++;
}
if (first < 0) {
items += first;
first = 0;
} else if (first + items > mPicker.values.length) {
items = mPicker.values.length - first;
}
for (int i = 0; i < items; i++) {
virtualViewIds.add(first + i);
}
}
@Override
protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
event.setContentDescription(mPicker.values[virtualViewId]);
}
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {
float itemWidth = mPicker.itemWidth + mPicker.dividerSize;
float scrollOffset = mPicker.getScrollX() - itemWidth * mPicker.sideItems;
int left = (int) (virtualViewId * itemWidth - scrollOffset);
int right = left + mPicker.itemWidth;
node.setContentDescription(mPicker.values[virtualViewId]);
node.setBoundsInParent(new Rect(left, 0, right, mPicker.getHeight()));
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
}
@Override
protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {
return false;
}
}
}
自定义
MyScaleView
package com.example.yangbin.pickm;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by Administrator on 2016/9/2 0002.
*/
public class MyScaleView extends View {
private Paint paint;
private int mWidth;
private int mHeight;
public MyScaleView(Context context) {
super(context);
init();
}
public MyScaleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyScaleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
paint = new Paint();
paint.setColor(Color.parseColor("#7E7E7E"));
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = getWidth();
mHeight = getHeight();
// 画一条线
canvas.drawLine(0f, mHeight, mWidth, mHeight, paint);
Path path = new Path();
path.moveTo(mWidth / 2, mHeight - 30);// 此点为多边形的起点
path.lineTo(mWidth / 2 - 20, mHeight);
path.lineTo(mWidth / 2 + 20, mHeight);
path.close(); // 使这些点构成封闭的多边形
canvas.drawPath(path, paint);
}
private int dp2px(int value) {
float v = getContext().getResources().getDisplayMetrics().density;
return (int) (v * value + 0.5f);
}
}