前段时间美工设计了一个控件,辛辛苦苦花了2天才弄出来,结果偶然看到了网上原来有开源项目...
这个控件可以上下滚动,通过adapter可以拿到中间选中部分的值。。
先上个效果图先:
再上代码。。
这个是控件的view
注意哦:这个只是中间的白色的滚动部分和绿色部分的代码,周围的背景需要自己用xml布局。。图片啥的就不发了,毕竟是在公司做的。。。而且我上的图是用了2个这个控件排在一起的效果
public class ScrollListView extends View { public static final String TAG = ScrollListView.class.getSimpleName(); static final int UP = 0; //滑动方向,向上 static final int DOWN = 1; //滑动方向,向下 private Context mContext; private GestureDetector mGestureDetector; private ScrollListViewAdapter<?> mData; private Paint mPaint; private int offset = 0; // 偏移量,向上递减,向下递增 // private int offsetGaol; private int mMaxPos; //滑到最后一条元素的坐标 private int mItemHeight = 50; // 一行元素的高度 private int mHalfItemHeight; //半行元素的高度 private int mHeight; //绘制内容高度 private int mHalfHeight; //内容高度的一半 private int mCurrentMiddleIndex; //当前控件正中间的元素在list的索引值 private int mFlingVelocity; //fling的速度 private int mTouchInitialOffset; //临时变量保存触摸前的offset private boolean mDragging; //是否在触摸状态 private float mStartTouchPos; //触摸起点坐标 private float mTextOffsetY; //字体居中偏移量 private boolean isCenter = false; // 让字居中的微调控制,为true进行调整,为false不进行调整 private int mDirect = -1; //方向 private Bitmap mTop; private Bitmap mBottom; private float mTextPaddingLeft = 10; /** * 控件触摸事件 * @author lhxia * */ public interface ScrollViewListener { /** * 触摸开始调用 * * @param y * 触摸第一个点的Y坐标 */ public void scrollViewTouchStart(float y); /** * 触摸移动调用 * * @param y * 当前触摸点的Y坐标 */ public void scrollViewTouchMove(float y); /** * 触摸完调用 */ public void scrollViewTouchEnd(); /** * 当fling的时候调用 * * @param vY * Y方向坐标 */ public void scrollViewFling(float vY); /** * 更新控件 */ public void scrollViewDraw(); /** * 速度为0时调用 */ public void onStop(int index); }; private ScrollViewListener mScrollViewListener = new ScrollViewListener() { @Override public void scrollViewTouchStart(float y) { Log.e(TAG, "on touch start"); mDragging = true; mStartTouchPos = y; mTouchInitialOffset = offset; mFlingVelocity = 0; } @Override public void scrollViewTouchMove(float y) { Log.e(TAG, "on move"); float dis = mStartTouchPos - y; setDirect(dis); offset = trap((int) (mTouchInitialOffset - dis)); updateDisplay(); } @Override public void scrollViewTouchEnd() { Log.e(TAG, "on touch end"); mDragging = false; // offsetGaol = offset; invalidate(); isCenter = true; } @Override public void scrollViewFling(float vY) { Log.e(TAG, "on fling"); mDragging = false; // offsetGaol = offset; mFlingVelocity = (int) (-vY); updateDisplay(); } @Override public void scrollViewDraw() { Log.d(TAG, "on draw"); if (isCenter) { updateDisplay(); isCenter = false; } else if (mFlingVelocity != 0) { updateDisplay(); } } @Override public void onStop(int index) { if(mData != null){ mData.onRefreshIndex(index); } } }; /** * 判断滑动方向 * @param dis 滑动距离 */ private void setDirect(float dis) { if (dis > 0) { mDirect = UP; } else { mDirect = DOWN; } } /** * 更新offset,并调用invalidate刷新界面 */ private void updateDisplay() { if (!mDragging) { int offsetDelta = 0; if (mFlingVelocity != 0) { //速度不为0时,使控件减速 Log.d(TAG, "speed down, v = " + mFlingVelocity); offsetDelta = mFlingVelocity / 40; // 速度递减 if (mFlingVelocity > 90) { mFlingVelocity -= 90; } else if (mFlingVelocity < -90) { mFlingVelocity += 90; } else { mFlingVelocity = 0; Log.d(TAG, "stop, v = " + mFlingVelocity); isCenter = true; } offset -= offsetDelta; if (offset <= -(mMaxPos - (mHeight / 2) - mItemHeight)) { offset = -(mMaxPos - (mHeight / 2) - mItemHeight); mFlingVelocity = 0; isCenter = true; Log.d(TAG, "stop, v = " + mFlingVelocity); } if (offset >= mHalfHeight) { offset = mHalfHeight; mFlingVelocity = 0; isCenter = true; Log.d(TAG, "stop, v = " + mFlingVelocity); } // offsetGaol = offset; } else { //当速度为0时,元素可能并没有居中,这里调整居中 int delta = (mHalfHeight - offset) % mItemHeight; if (delta <= mHalfItemHeight - 10 && delta != 0) { delta += 1; } else if (delta > mHalfItemHeight + 10) { delta -= 1; } else { if (delta >= mHalfItemHeight) { delta += 1; } else if (delta < mHalfItemHeight) { delta -= 1; } } if (delta != 0) { isCenter = true; } if (mDirect == DOWN) { Log.d(TAG, "direct down"); offset += delta; } else if (mDirect == UP) { Log.d(TAG, "direct up"); offset += delta; } if(mScrollViewListener != null){ mScrollViewListener.onStop(computeCurrentMiddlePosition()); } } } invalidate(); } /** * 计算在数据的list中,当前显示在控件正中间的index * * @return 当前控件中间的元素的索引 */ private int computeCurrentMiddlePosition() { double f; if (offset <= 0) { f = ((-offset) + mHalfHeight) * 1.0 / mItemHeight; } else { f = (mHalfHeight - offset) * 1.0 / mItemHeight; } mCurrentMiddleIndex = (int) Math.round(f); if(mData != null){ Log.d(TAG, "compute CurrentMiddlePosition is : " + mCurrentMiddleIndex + " size " + mData.size() + " offset : " + offset + "\n" + "halfheight" + mHalfHeight); }else { return -1; } return mCurrentMiddleIndex; } /** * 检测offset的范围 * @param pos * @return 返回检测后的offset */ private int trap(int pos) { if (pos >= mHeight / 2) return mHeight / 2; if (pos <= -(mMaxPos - (mHeight / 2) - mItemHeight)) return -(mMaxPos - (mHeight / 2) - mItemHeight); return pos; } /** * 获取当前中间的元素在list里面的index * @return */ public int getMiddleIndex(){ computeCurrentMiddlePosition(); return mCurrentMiddleIndex; } /** * 画9.png图片 * @param c canvas * @param id 图片id * @param r1 图片范围 */ private void drawNinepath(Canvas c, int id, Rect r1) { Bitmap bmp = BitmapFactory.decodeResource(getResources(), id); NinePatch patch = new NinePatch(bmp, bmp.getNinePatchChunk(), null); patch.draw(c, r1); } public ScrollListView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mScrollViewListener != null) { mScrollViewListener.scrollViewFling(velocityY); } return true; } }); TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.ScrollListView); int textColor = type.getColor(R.styleable.ScrollListView_textColor, 0XFF00FF00); //提供默认值,放置未指定 float textSize = type.getDimension(R.styleable.ScrollListView_textSize, 25); mTextPaddingLeft = type.getDimension(R.styleable.ScrollListView_paddingLeft, 25); type.recycle(); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setTextSize(textSize); mTextOffsetY = (mItemHeight - mPaint.getTextSize()) / 2; mPaint.setColor(textColor); mPaint.setTypeface(Typeface.SANS_SERIF); mTop = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.bg_condition_top_alpha); mBottom = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.bg_condition_bottom_alpha); } private void init(){ if(mData != null){ mMaxPos = mData.size() * mItemHeight; } offset = mHalfHeight; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mHeight = MeasureSpec.getSize(heightMeasureSpec); mHalfHeight = mHeight / 2; mHalfItemHeight = mItemHeight / 2; offset = mHalfHeight; // offsetGaol = offset; computeCurrentMiddlePosition(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // // 画中间线,用于调试 // canvas.drawLine(0, mHalfHeight, getMeasuredWidth(), mHalfHeight, // mPaint); // canvas.drawLine(0, mHalfHeight - mItemHeight, getMeasuredWidth(), // mHalfHeight - mItemHeight, mPaint); float y = 0; if(mData != null){ int size = mData.size(); String tmp; for (int i = 0; i < size; i++) { y = i* mItemHeight + offset - mTextOffsetY + 20; tmp = mData.getValue(i); if(tmp != null && y > -20 || y < mHeight + 20){ canvas.drawText(tmp, mTextPaddingLeft, i * mItemHeight + offset - mTextOffsetY + 20, mPaint); } } if (mScrollViewListener != null) { mScrollViewListener.scrollViewDraw(); } } // canvas.drawLine(0, offset, getMeasuredWidth(), offset, mPaint); drawNinepath(canvas, R.drawable.bg_condition_checked, new Rect(0, (int)(mHalfHeight - mHalfItemHeight + 2), getMeasuredWidth(), (int)(mHalfHeight + mHalfItemHeight - 1))); //画2端渐变蒙版 // drawNinepath(canvas, R.drawable.bg_condition_top_1, // new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight())); canvas.drawBitmap(mTop, 0, 0, mPaint); canvas.drawBitmap(mBottom, 0, mHeight - 4.3f, mPaint); } @Override public boolean onTouchEvent(MotionEvent event) { if (mGestureDetector.onTouchEvent(event)) { return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mScrollViewListener.scrollViewTouchStart(event.getY()); break; case MotionEvent.ACTION_MOVE: mScrollViewListener.scrollViewTouchMove(event.getY()); break; case MotionEvent.ACTION_UP: mScrollViewListener.scrollViewTouchEnd(); break; } return true; } public void setAdapter(ScrollListViewAdapter<?> adapter){ mData = adapter; init(); invalidate(); } public ScrollListViewAdapter<?> getAdapter(){ return mData; } }
这个是adapter,用来把数据绑定到view上的:
public abstract class ScrollListViewAdapter<T> { public abstract String getValue(int index); public abstract int size(); /** * 当控件滚动的时候由控件调用 * @param index 索引 * @return */ public abstract int onRefreshIndex(int index); public abstract String getId(int index); public abstract T getItem(int index); public abstract void setList(List<T> data); public ScrollListViewAdapter(List<T> data){ } }
代码写的不好。。如果有什么疑问的请指教。。