滚动选择器

最近项目用到滚动选择器,网上找了很多,效果都不是很好,其中一个还不可以,但是没有fling效果,于是就自己动手改了改。

总共就一个自定义View,一个文件解决。

public class StringPicker extends View {
	/** 自动回滚到中间的速度 */
	private static final float SPEED = 2;
	private List<String> dataList = new ArrayList<>();
	/** 选中的位置,这个位置是dataList的中心位置,一直不变 */
	private int currentSelected;
	private Paint paint;
	private float selectedTextSize = -1;
	private float normalTextSize = -1;
    private float textSpace = -1;
	private int selectedTextColor = 0xFF00839A;
	private int normalTextColor = 0x66666666;
	private int height;
	private int width;
	private float lastDownY;
	/** 滑动的距离 */
	private float moveLen = 0;
	private boolean isInit = false;
	private onSelectListener selectListener;
	private Timer timer;
	private MyTimerTask task;
	private GestureDetector gestureDetector;
	private Scroller scroller;
	private boolean isFling;
	
	public StringPicker(Context context) {
		this(context, null);
	}

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

	private void init() {
		timer = new Timer();
		paint = new Paint(Paint.ANTI_ALIAS_FLAG);
		paint.setStyle(Paint.Style.FILL);
		paint.setTextAlign(Paint.Align.CENTER);        
		gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
			@Override
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
				isFling = true;
				scroller.fling((int) e2.getX(), (int) e2.getY(), 0, (int)velocityY, 0, 0, -2000, 2000);
				return super.onFling(e1, e2, velocityX, velocityY);
			}
		});
		scroller = new Scroller(getContext());
	}

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        height = getHeight();
        width = getWidth();
        if (selectedTextSize == -1 || normalTextSize == -1) {
            selectedTextSize = height / 3f;
            normalTextSize = selectedTextSize / 2f;
        }
        if (textSpace == -1) textSpace = normalTextSize * 1.7f;
        isInit = true;
    }
    
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		// 根据index绘制view
		if (isInit)
			drawData(canvas);
	}

	@Override
	public void computeScroll() {
		if (scroller.computeScrollOffset()) {
			if (isFling) doMove(null);
		} else {
			if (isFling) {
				doUp(null);
				isFling = false;
			}
		}
	}
	
	public void setOnSelectListener(onSelectListener listener) {
		selectListener = listener;
	}

	public void setTextColor(int selectedTextColor, int normalTextColor) {
		this.selectedTextColor = selectedTextColor;
		this.normalTextColor = normalTextColor;
        postInvalidate();
	}
	
	public void setTextSize(float selectedTextSize, float normalTextSize) {
		this.selectedTextSize = selectedTextSize;
		this.normalTextSize = normalTextSize;
        postInvalidate();
	}
	
    /**字体间距,指未选中字体间距*/
    public void setTextSpace(float space) {
        textSpace = space;
    }
    
    public void setTypeface(Typeface typeface) {
        paint.setTypeface(typeface);
        postInvalidate();
    }
    
	private void performSelect() {
		if (selectListener != null)
			selectListener.onSelect(dataList.get(currentSelected));
	}

	public void setData(List<String> dataList) {
		if (dataList == null || dataList.size() == 0) return;
		this.dataList = dataList;
		currentSelected = dataList.size() / 2;
		postInvalidate();
	}

	/**
	 * 选择选中的item的index
	 */
	public void setSelected(String item) {
        for (int i = 0; i < dataList.size(); i++) {
            if (dataList.get(i).equals(item)) {
                currentSelected = i;
            }
        }
		int distance = dataList.size() / 2 - currentSelected;
		if (distance < 0) {
			for (int i = 0; i < -distance; i++) {
				moveHeadToTail();
				currentSelected--;
			}
		} else if (distance > 0) {
			for (int i = 0; i < distance; i++) {
				moveTailToHead();
				currentSelected++;
			}
		}
		performSelect();
        postInvalidate();
	}

	private void drawData(Canvas canvas) {
		// 先绘制选中的text再往上往下绘制其余的text
		float scale = parabola(height / 4.0f, moveLen);
		float size = (selectedTextSize - normalTextSize) * scale + normalTextSize;
		paint.setTextSize(size);
		paint.setColor(getColor(scale));
		// text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标
		float x = width / 2f;
		float y = height / 2f + moveLen;
		Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
		float baseline = y - (fmi.bottom / 2f + fmi.top / 2f);

		int indexs = currentSelected;
		String textData = dataList.get(indexs);
		canvas.drawText(textData, x, baseline, paint);

		// 绘制上方data
		for (int i = 1; (currentSelected - i) >= 0; i++) {
			drawOtherText(canvas, i, -1);
		}
		// 绘制下方data
		for (int i = 1; (currentSelected + i) < dataList.size(); i++) {
			drawOtherText(canvas, i, 1);
		}
	}

	/**
	 * @param position 距离mCurrentSelected的差值
	 * @param type 1表示向下绘制,-1表示向上绘制
	 */
	private void drawOtherText(Canvas canvas, int position, int type) {
		float d = textSpace * position + type * moveLen;
		float scale = parabola(height / 4.0f, d);
		float size = (selectedTextSize - normalTextSize) * scale + normalTextSize;
		paint.setTextSize(size);
		paint.setColor(getColor(scale));        
		float y = (float) (height / 2.0 + type * d);
		Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
		float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));

		int indexs = currentSelected + type * position;
		String textData = dataList.get(indexs);
		canvas.drawText(textData, (float) (width / 2.0), baseline, paint);
	}

	private int getColor(float scale) {
		int Aa = normalTextColor >> 24 & 0xff;
		int Ra = normalTextColor >> 16 & 0xff;
		int Ga = normalTextColor >> 8 & 0xff;
		int Ba = normalTextColor & 0xff;
		int Ab = selectedTextColor >> 24 & 0xff;
		int Rb = selectedTextColor >> 16 & 0xff;
		int Gb = selectedTextColor >> 8 & 0xff;
		int Bb = selectedTextColor & 0xff;
		int a = (int) (Aa + (Ab - Aa) * scale);
		int r = (int) (Ra + (Rb - Ra) * scale);
		int g = (int) (Ga + (Gb - Ga) * scale);
		int b = (int) (Ba + (Bb - Ba) * scale);
		return Color.argb(a, r, g, b);
	}

	/**
	 * 抛物线 
	 * @param zero 零点坐标
	 * @param x 偏移量
	 */
	private float parabola(float zero, float x) {
		float f = (float) (1 - Math.pow(x / zero, 2));
		return f < 0 ? 0 : f;
	}
	
	private void moveHeadToTail() {
		dataList.add(dataList.remove(0));
	}

	private void moveTailToHead() {
		dataList.add(0, dataList.remove(dataList.size() - 1));
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		gestureDetector.onTouchEvent(event);

		switch (event.getActionMasked()) {
			case MotionEvent.ACTION_DOWN:
				doDown(event);
				break;
			case MotionEvent.ACTION_MOVE:
				doMove(event);
				break;
			case MotionEvent.ACTION_UP:
				doUp(event);
				break;
		}
		return true;
	}

	private void doDown(MotionEvent event) {
		if(!scroller.isFinished()) {
			scroller.abortAnimation();
		}
		if (task != null) {
			task.cancel();
			task = null;
		}
		lastDownY = event.getY();
	}

	private void doMove(MotionEvent event) {
		float currY;
		if (event == null) {
			currY = scroller.getCurrY();
		} else {
			currY = event.getY();
		}
		moveLen += currY - lastDownY;

		if (moveLen > textSpace / 2) {
			// 往下滑超过离开距离
			moveTailToHead();
			moveLen = moveLen - textSpace;
		} else if (moveLen < -textSpace / 2) {
			// 往上滑超过离开距离
			moveHeadToTail();
			moveLen = moveLen + textSpace;
		}
		lastDownY = currY;
		invalidate();
	}

	private void doUp(MotionEvent event) {
		// 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置
		if (Math.abs(moveLen) < 0.0001) {
			moveLen = 0;
			return;
		}
		if (task != null) {
			task.cancel();
			task = null;
		}
		task = new MyTimerTask(updateHandler);
		timer.schedule(task, 0, 10);
	}

	class MyTimerTask extends TimerTask {
		Handler handler;

		public MyTimerTask(Handler handler) {
			this.handler = handler;
		}

		@Override
		public void run() {
			handler.sendMessage(handler.obtainMessage());
		}

	}

	public interface onSelectListener {
		void onSelect(String text);
	}
	
	final Handler updateHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			if (Math.abs(moveLen) < SPEED) {
				moveLen = 0;
				if (task != null) {
					task.cancel();
					task = null;
					performSelect();
				}
			} else
				// 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚
				moveLen = moveLen - moveLen / Math.abs(moveLen) * SPEED;
			invalidate();
		}
	};
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.zfs.stringpicker.MainActivity">

    <com.zfs.stringpicker.StringPicker
        android:id="@+id/string_picker"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>
</RelativeLayout>

MainActivity中使用:

public class MainActivity extends AppCompatActivity {
	
	private Toast toast;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
		StringPicker stringPicker = (StringPicker) findViewById(R.id.string_picker);
		List<String> list = new ArrayList<>();
		for (int i = 1900; i <= 2016; i++) {
			list.add(String.valueOf(i));
		}
		stringPicker.setData(list);
		try {
			stringPicker.setTypeface(Typeface.createFromAsset(getAssets(), "slender.ttf"));
		} catch (Exception e){
			e.printStackTrace();
		}
		//滚动监听
		stringPicker.setOnSelectListener(new StringPicker.onSelectListener() {
			@Override
			public void onSelect(String text) {
				toast.setText(text);
				toast.show();
			}
		});
	}
}


demo下载:https://github.com/fszeng2011/StringPicker

猜你喜欢

转载自blog.csdn.net/fszeng2011/article/details/51723824