Android自定义View 时段选择器

来源:互联网 发布:网络用英语怎么说 编辑:程序博客网 时间:2024/06/05 07:56

先看下效果

这里写图片描述

一开始做的gif一直太大了,无法上传,只能调整了分辨率和播放时间,别嫌看不清~

大致用语言描述下,就是一个选择时间段的自定义view,全部都是通过canvas绘制,红色块表示无法选择,蓝色表示可选择,通过手指拖动蓝色块,拖动蓝色块下方白色小点改变蓝色块的大小,当出现红蓝色块重叠时,蓝色块变色为橙色。

接下来就是代码啦,仔细看注释哦

import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Point;import android.graphics.Rect;import android.graphics.RectF;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewParent;import android.widget.ScrollView;import com.asiainfo.banbanapp.tools.LocalDisplay;import com.asiainfo.banbanapp.tools.LogUtil;import java.util.ArrayList;import java.util.List;/** * Created by hubert * <p> * Created on 2017/6/7. */public class TimeSectionPicker extends View implements View.OnTouchListener {    public static final int TYPE_MOVE = 1;    public static final int TYPE_EXTEND = 2;    public static final int TYPE_CLICK = 3;    private static String[] titles = {"09:00", "09:30", "10:00", "10:30", "11:00", "11:30", "12:00", "12:30"            , "13:00", "13:30", "14:00", "14:30", "15:00", "15:30", "16:00", "16:30", "17:00", "17:30", "18:00"};    private static String subTitle = "30m";    private int lineColor = Color.parseColor("#dedede");    private int lightTitleColor = Color.parseColor("#71baff");    private int titleColor = Color.parseColor("#666666");    private int textSize = LocalDisplay.dp2px(12);    private int textColor = Color.parseColor("#fefefe");    private int bookColor = Color.parseColor("#71baff");    private int bookStrokeColor = Color.parseColor("#71baff");    private int usedColor = Color.parseColor("#f3928a");    private int usedStrokeColor = Color.parseColor("#f3928a");    private int overdueColor = Color.parseColor("#c7c7c7");    private int overlappingColor = Color.parseColor("#ff9971");    private float round = 10f;//区域圆角    private float extendPointR = LocalDisplay.dp2px(8);//拉伸点半径    private int space = LocalDisplay.dp2px(25);//刻度间隔    private int offset = 100;//短线偏移量    private boolean isFrist = true;//初始化padding和宽高值    private int type;//移动.扩展拉伸.点击    private Paint mPaint;    private Point p1;    private Point p2;    private Rect titleBounds;    private RectF bookRect;    private RectF usedRect;    private int paddingTop;    private int paddingLeft;    private int width;    private float downY;    private int bookStart = -1;    private int bookCount = 0;    private List<int[]> used = new ArrayList<>();    private List<RectF> usedAreas = new ArrayList<>();    private int lineNumber;    private RectF extendPointRect;    private float bottom;    public int[] overdue;    private OverlappingStateChangeListener listener;    private boolean lastState;    private OnBookChangeListener bookChangeListener;    public TimeSectionPicker(Context context) {        this(context, null);    }    public TimeSectionPicker(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public TimeSectionPicker(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        setOnTouchListener(this);        init();    }    private void init() {        mPaint = new Paint();        mPaint.setAntiAlias(true);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setTextSize(textSize);        titleBounds = new Rect();        mPaint.getTextBounds(titles[0], 0, titles[0].length(), titleBounds);        p1 = new Point();        p2 = new Point();        bookRect = new RectF();        usedRect = new RectF();    }    public void setOverdue(int[] overdue) {        this.overdue = overdue;    }    public void setBookArea(int start, int count) {        LogUtil.huI("start:" + start + "/count:" + count);        bookStart = start;        bookCount = count;        setBookRect(start, count);        postInvalidate();    }    public void clearBookArea() {        bookStart = -1;        bookCount = 0;        setBookRect(0, 0);        postInvalidate();    }    public void addUsed(int[] area) {        used.add(area);        postInvalidate();    }    public List<int[]> getUsed() {        return used;    }    public void clearUsed() {        used.clear();        overdue = null;        postInvalidate();    }    public int getTimeNumber(int hour, int minute) {        int result = (hour - 9) * 2;        if (minute > 30) {            result += 2;        } else if (minute > 0) {            result += 1;        }        if (result > titles.length - 1) {            result = titles.length - 1;        }        return result;    }    public String[] getBookTime() {        if (bookStart == -1) {            return null;        }        String[] strings = new String[2];        strings[0] = titles[bookStart];        strings[1] = titles[bookStart + bookCount];        return strings;    }    public int getBookCount() {        return bookCount;    }    private String getTimeString(int start, int count) {        StringBuilder sb = new StringBuilder();        for (int i = 0; i < titles.length; i++) {            if (start == i) {                sb.append(titles[i]);                sb.append("~");            }            if (start + count == i) {                sb.append(titles[i]);            }        }        return sb.toString();    }    public void setOverlappingStateChangeListener(OverlappingStateChangeListener listener) {        this.listener = listener;    }    public void setBookChangeListener(OnBookChangeListener bookChangeListener) {        this.bookChangeListener = bookChangeListener;    }    public boolean isOverlapping() {        if (bookCount == 0) {            return false;        }        for (RectF usedArea : usedAreas) {            if (usedArea.intersect(bookRect)) {                return true;            }        }        return false;    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int width;        int height;        if (widthMode == MeasureSpec.EXACTLY) {            width = widthSize;        } else {            width = 800;//wrap_content的宽        }        if (heightMode == MeasureSpec.EXACTLY) {            height = heightSize;        } else {            height = space * titles.length;//wrap_content的高        }        setMeasuredDimension(width, height);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (isFrist) {//初始化参数            //处理padding            paddingTop = getPaddingTop();            int paddingBottom = getPaddingBottom();            paddingLeft = getPaddingLeft();            int paddingRight = getPaddingRight();            width = getWidth() - paddingLeft - paddingRight;            int height = getHeight() - paddingTop - paddingBottom;            lineNumber = titles.length;            LogUtil.huI("titles.length:" + titles.length);            bookRect.set(paddingLeft + 180, paddingTop + space * bookStart                    , width - 30, paddingTop + space * (bookStart + bookCount));            usedRect.set(paddingLeft + 180, paddingTop, width - 30, paddingTop);            bottom = paddingTop + space * (titles.length - 1);            isFrist = false;        }        //预定框与已预定是否交叠        boolean overlapping = isOverlapping();        if (overlapping != lastState && listener != null) {            listener.onOverlappingStateChanged(overlapping);            lastState = overlapping;        }        //画刻度线        mPaint.setColor(lineColor);        for (int i = 0; i < lineNumber; i++) {            p1.set(i % 2 == 1 ? paddingLeft + offset : paddingLeft, paddingTop + space * i);            p2.set(width, paddingTop + space * i);            canvas.drawLine(p1.x, p1.y, p2.x, p2.y, mPaint);        }        //画时间文字        mPaint.setTextAlign(Paint.Align.LEFT);        for (int i = 0; i < lineNumber; i++) {            if (i >= bookStart && i <= bookStart + bookCount) {                mPaint.setColor(lightTitleColor);            } else {                mPaint.setColor(titleColor);            }            if (i % 2 == 0) {                canvas.drawText(titles[i], paddingLeft, paddingTop + titleBounds.height() * 1.3f + space * i, mPaint);            } else {                if (i == bookStart || i == bookStart + bookCount) {                    canvas.drawText(subTitle, paddingLeft + titleBounds.width() / 2, paddingTop + titleBounds.height() * 1.2f + space * i, mPaint);                }            }        }        //画已使用区域        usedAreas.clear();        for (int[] ints : used) {            RectF rectF = new RectF();            rectF.set(usedRect.left, usedRect.top + space * ints[0]                    , usedRect.right, usedRect.bottom + space * (ints[0] + ints[1]));            usedAreas.add(rectF);            drawUsedRect(rectF, canvas, mPaint, "会议室已预定 " + getTimeString(ints[0], ints[1]));        }        //画过期的区域        if (overdue != null) {            RectF rectF = new RectF();            rectF.set(usedRect.left, usedRect.top + space * overdue[0]                    , usedRect.right, usedRect.bottom + space * (overdue[0] + overdue[1]));            usedAreas.add(rectF);            mPaint.setStyle(Paint.Style.FILL);            mPaint.setColor(overdueColor);            canvas.drawRoundRect(rectF, round, round, mPaint);        }        //画预定区域        drawBookRect(canvas, mPaint, overlapping);    }    private void drawUsedRect(RectF rectF, Canvas canvas, Paint paint, String text) {        paint.setStyle(Paint.Style.STROKE);        paint.setColor(usedStrokeColor);        canvas.drawRoundRect(rectF, round, round, paint);        paint.setStyle(Paint.Style.FILL);        paint.setColor(usedColor);        canvas.drawRoundRect(rectF, round, round, paint);        //不需要文字了//        paint.setTextAlign(Paint.Align.CENTER);//        paint.setColor(textColor);//        canvas.drawText(text, rectF.centerX(), rectF.centerY(), paint);    }    public void drawBookRect(Canvas canvas, Paint paint, boolean overlapping) {        if (bookCount == 0) {            return;        }        paint.setStyle(Paint.Style.STROKE);        paint.setColor(overlapping ? overlappingColor : bookStrokeColor);        canvas.drawRoundRect(bookRect, round, round, paint);        paint.setStyle(Paint.Style.FILL);        paint.setColor(overlapping ? overlappingColor : bookColor);        canvas.drawRoundRect(bookRect, round, round, paint);        paint.setTextAlign(Paint.Align.CENTER);        paint.setColor(textColor);        canvas.drawText(overlapping ? "该时段不可预定" : ("会议室预定 " + getTimeString(bookStart, bookCount))                , bookRect.centerX(), bookRect.centerY(), paint);        paint.setColor(Color.WHITE);        canvas.drawCircle(bookRect.centerX(), bookRect.bottom, extendPointR, paint);        paint.setColor(overlapping ? overlappingColor : bookStrokeColor);        paint.setStyle(Paint.Style.STROKE);        canvas.drawCircle(bookRect.centerX(), bookRect.bottom, extendPointR, paint);        extendPointRect = new RectF(bookRect.centerX() - extendPointR * 2, bookRect.bottom - extendPointR * 2                , bookRect.centerX() + extendPointR * 2, bookRect.bottom + extendPointR * 2);        //查看扩展点触发区域//        paint.setColor(Color.BLACK);//        Log.i("tag", extendPointRect.toString());//        canvas.drawRect(extendPointRect, paint);    }    @Override    public boolean onTouch(View v, MotionEvent event) {        //view独享事件,即父view不可以获取后续事件,scrollview默认是false        getParent().requestDisallowInterceptTouchEvent(true);        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                float x = event.getX();                downY = event.getY();                Log.i("tag", "action down -- x,y:" + x + "," + downY);                if (extendPointRect != null && extendPointRect.contains(x, downY)) {                    type = TYPE_EXTEND;                    return true;                }                if (bookRect.contains(x, downY)) {                    type = TYPE_MOVE;                    return true;                }                if (bookCount == 0 && checkClick(downY) && x > 150) {                    type = TYPE_CLICK;                    return true;                }                return false;            case MotionEvent.ACTION_MOVE:                float currentY = event.getY();//                Log.i("tag", "action move -- y:" + currentY);                float dY = currentY - downY;                //外层联动                ViewParent p = getParent();                if (p instanceof ScrollView && type != TYPE_CLICK) {                    ScrollView parent = (ScrollView) p;                    parent.scrollBy(0, (int) dY / 2);                }                if (bookChangeListener != null) {                    bookChangeListener.onBookChange();                }                switch (type) {                    case TYPE_MOVE:                        bookRect.set(bookRect.left, bookRect.top + dY, bookRect.right, bookRect.bottom + dY);                        bookStart = Math.round((bookRect.top - paddingTop) / space);                        //边缘修正                        if (bookRect.top < paddingTop) {                            bookStart = 0;                            setBookRect(bookStart, bookCount);                        }                        if (bookRect.bottom > bottom) {                            bookStart = titles.length - 1 - bookCount;                            setBookRect(bookStart, bookCount);                        }                        break;                    case TYPE_EXTEND:                        bookRect.set(bookRect.left, bookRect.top, bookRect.right, bookRect.bottom + dY);                        int end = (int) ((bookRect.bottom - paddingTop) / space);                        bookCount = end - bookStart;                        if (bookCount < 1) {                            bookCount = 1;                            setBookRect(bookStart, bookCount);                        }                        if (bookRect.bottom > bottom) {                            end = titles.length - 1;                            bookCount = end - bookStart;                            setBookRect(bookStart, bookCount);                        }                        break;                    case TYPE_CLICK:                        break;                }                downY = currentY;                postInvalidate();                break;            case MotionEvent.ACTION_UP://                Log.i("tag", "action up --");                switch (type) {                    case TYPE_MOVE:                        if (bookRect.top < paddingTop) {                            bookStart = 0;                        }                        break;                    case TYPE_EXTEND:                        int end = Math.round((bookRect.bottom - paddingTop) / space);                        if (bookRect.bottom > bottom) {                            end = titles.length - 1;                        }                        bookCount = end - bookStart;                        break;                    case TYPE_CLICK:                        bookStart = (int) ((downY - paddingTop) / space);                        if (bookStart > titles.length - 1 - 2) {                            bookStart = titles.length - 1 - 2;                        }                        bookCount = 2;                        break;                }                setBookRect(bookStart, bookCount);                postInvalidate();                break;        }        return false;    }    private boolean checkClick(float y) {        for (RectF rectF : usedAreas) {            if (y >= rectF.top && y <= rectF.bottom) {                return false;            }        }        //防止点击最下方边界外也绘制book区域        int max = paddingTop + (lineNumber - 2) * space;        LogUtil.huI("max:" + max);        return y <= max;    }    private void setBookRect(int start, int count) {        if (bookChangeListener != null) {            bookChangeListener.onBookCountChanged(count);        }        bookRect.set(bookRect.left, paddingTop + space * start                , bookRect.right, paddingTop + space * (start + count));    }    public interface OverlappingStateChangeListener {        void onOverlappingStateChanged(boolean isOverlapping);    }    public interface OnBookChangeListener {        void onBookChange();        void onBookCountChanged(int bookCount);    }}

有兴趣的可以直接复制过去试试,估计只有这个LocalDisplay.dp2px(8)会报红,知识一个dp转px的工具,网上很多。
使用时候建议外面嵌套一层ScrollView,这样可以控制控件的大小而不会显得被压缩

附赠稍作修改的ScrollView,只是判断滑动数字区域必然移动ScrollView

/** * Created by hubert * <p> * Created on 2017/6/8. */public class TimeSectionScroller extends ScrollView {    public TimeSectionScroller(Context context) {        super(context);    }    public TimeSectionScroller(Context context, AttributeSet attrs) {        super(context, attrs);    }    public TimeSectionScroller(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                float x = ev.getX();                if (x < 150) {                    return true;                }                break;            case MotionEvent.ACTION_HOVER_MOVE:                break;            case MotionEvent.ACTION_UP:                break;        }        return super.onInterceptTouchEvent(ev);    }}

小提示:
添加使用的区块,getTimeNumber第一个参数为24制的小时数,第二个参数为分钟数

start = timeSectionPicker.getTimeNumber(13, 0);end = timeSectionPicker.getTimeNumber(18, 0);timeSectionPicker.addUsed(new int[]{start, end});
原创粉丝点击