自定义view-车型评分统计图

来源:互联网 发布:淘宝全屏海报设置 编辑:程序博客网 时间:2024/04/28 08:33

        先看效果,如下图:

         

        一。做一个效果之前,先分析由几种图形组成

        1.画横、纵坐标和刻度

        2.画柱形图

        3.手势的监听和页面打开时自动滑动

         

         二, 界面实现,分析各个组件和效果的实现

        1.画x,y轴和坐标

        画x轴,首先先计算view的总高度,减去上面的空白高度除于分数,就可以得出,x轴各个数字所在的位置,代码如下

   

 float textHeight = mHeight + paddingTop - bottomHeight;//横坐标高度        vertialInterval = (textHeight - 200f) / 5;        canvas.drawLine(startChart,0,startChart,textHeight,mLinePaint);        canvas.drawLine(startChart,textHeight,mWidth,textHeight,mLinePaint);

    
 for (int i=0; i < vertileText.length; i++){            float y =  (vertialInterval*i);            mVertileTextpaint.getTextBounds(vertileText[i],0,vertileText[i].length(),mBound);            canvas.drawText(vertileText[i],0,textHeight - y  + mBound.height() / 2,mVertileTextpaint);            if (i > 0)            canvas.drawLine(startChart,textHeight - y,startChart + 10,textHeight - y,mVertileTextpaint);        }
      画Y轴,后面的柱形图的坐标用前面的坐标不断叠加,这里要控制滑动的区域,所以要剪裁canvas,要排除纵标左方的区域,这里裁剪使用

     

  canvas.clipRect(startChart + 10, 0, mWidth, getHeight(), Region.Op.REPLACE);
    前面表示裁剪的left,top,后面参数表示right,bottom坐标 ,接下来就可以画柱形图,和x轴刻度了

    

 float chartTempStart = startChart + xInterVal;        float maxY = textHeight - 200f;        for (int i=0; i < horText.length; i++){            float height =  200f +  (10f - barDatas.get(i).getNum()) / 10f * maxY;            String num = String.valueOf(barDatas.get(i).getNum());            mVertileTextpaint.getTextBounds(num,0,num.length(),mBound);            canvas.drawText(num,chartTempStart + mCurrentOrigin.x,height - mBound.height(),mVertileTextpaint);            RectF rectF = new RectF();            rectF.left = chartTempStart + mCurrentOrigin.x;            rectF.top = height;            rectF.right = chartTempStart + yellowBarWidth + mCurrentOrigin.x;            rectF.bottom = textHeight - 3;            canvas.drawRect(rectF,mChartPaint);            chartTempStart += ( barWidth + startChart);        }        float grayBarStart = startChart + xInterVal ;        for (int i=0; i < horText.length; i++){            float height =  200f +  (10f - avgDatas.get(i).getNum()) / 10f * maxY;            RectF rectF = new RectF();            rectF.left = grayBarStart  + (yellowBarWidth + barInterval) + mCurrentOrigin.x;            rectF.top = height;            rectF.right = grayBarStart +  (yellowBarWidth + barInterval) + grayBarWidth + mCurrentOrigin.x;            rectF.bottom = textHeight - 3;            canvas.drawRect(rectF,mGrayPaint);            grayBarStart += (barWidth + startChart );        }        float textTempStart = textStart;        for (int i=0; i < horText.length; i++){            mVertileTextpaint.getTextBounds(horText[i],0,horText[i].length(),mBound);            canvas.drawText(horText[i],textTempStart - mBound.width()/2 + mCurrentOrigin.x,mHeight - bottomHeight + mBound.height() + 38f,mVertileTextpaint);            textTempStart += (bottomHeight + startChart);        }
    三。接下是手势的处理,这是这个view主要的学习点

     创建手势对象和监听器

     

 mGestureDetector = new GestureDetectorCompat(context, mGestureListener);
  private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {        //手指按下        @Override        public boolean onDown(MotionEvent e) {//            goToNearestBar();            return true;        }        //有效的滑动        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {            switch (mCurrentScrollDirection) {                case NONE:                    // 只允许在一个方向上滑动                    if (Math.abs(distanceX) > Math.abs(distanceY)) {                        if (distanceX > 0) {                            mCurrentScrollDirection = Direction.LEFT;                        } else {                            mCurrentScrollDirection = RIGHT;                        }                    } else {                        mCurrentScrollDirection = Direction.VERTICAL;                    }                    break;                case LEFT:                    // Change direction if there was enough change.                    if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX < 0)) {                        mCurrentScrollDirection = RIGHT;                    }                    break;                case RIGHT:                    // Change direction if there was enough change.                    if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX > 0)) {                        mCurrentScrollDirection = Direction.LEFT;                    }                    break;            }            // 重新计算滑动后的起点            switch (mCurrentScrollDirection) {                case LEFT:                case RIGHT:                    mCurrentOrigin.x -= distanceX * mXScrollingSpeed;                    ViewCompat.postInvalidateOnAnimation(CommentBar.this);                    break;            }            Log.e(" mCurrentOrigin.x " , mCurrentOrigin.x + "");            return true;        }        //快速滑动        @Override        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {            if ((mCurrentFlingDirection == Direction.LEFT && !mHorizontalFlingEnabled) ||                    (mCurrentFlingDirection == RIGHT && !mHorizontalFlingEnabled)) {                return true;            }            mCurrentFlingDirection = mCurrentScrollDirection;            mScroller.forceFinished(true);            switch (mCurrentFlingDirection) {                case LEFT:                case RIGHT:                    mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, (int) (velocityX * mXScrollingSpeed), 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);                    break;                case VERTICAL:                    break;            }            ViewCompat.postInvalidateOnAnimation(CommentBar.this);            Log.e(" mCurrentOrigin.x2 " , mCurrentOrigin.x + "");            return true;        }        //单击事件        @Override        public boolean onSingleTapConfirmed(MotionEvent e) {            return super.onSingleTapConfirmed(e);        }        //长按        @Override        public void onLongPress(MotionEvent e) {            super.onLongPress(e);        }    };

主要是处理快速滑动和滑动这两种手势,在快速移动的时候用到了OverScroer对象,这里的使用方法,在这里就不介绍了,这边主要是实现了他的一个回调方法,在回调方法处理相应的事件

  @Override    public void computeScroll() {        super.computeScroll();        if (mScroller.isFinished()) {//当前滚动是否结束            if (mCurrentFlingDirection != Direction.NONE) {//                goToNearestBar();            }        } else {            if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) { //惯性滑动时保证最左边条目展示正确//                goToNearestBar();            } else if (mScroller.computeScrollOffset()) {//滑动是否结束 记录最新的滑动的点 惯性滑动处理                mCurrentOrigin.y = mScroller.getCurrY();                mCurrentOrigin.x = mScroller.getCurrX();                ViewCompat.postInvalidateOnAnimation(this);            }        }    }

最后就是view首次的加载动画,这里是创建差值器,不断的去改变x轴的偏移量

   mValueAnimator = ValueAnimator.ofFloat(0,(getWidth()  + xInterVal - (horText.length * bottomHeight + (horText.length ) * vertialInterval + startChart)),0);        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()        {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator)            {                Log.e(" valueAnimator ","" +   valueAnimator.getAnimatedValue());                mCurrentOrigin.x = (float) valueAnimator.getAnimatedValue();                invalidate();            }        });        mValueAnimator.setDuration(2500);        mValueAnimator.start();

到这里所有效果就分析完毕,完整代码如下,大家结合之后就更加清晰易懂了

package com.socks.scrollerdemo;import android.animation.Animator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PointF;import android.graphics.Rect;import android.graphics.RectF;import android.graphics.Region;import android.os.Build;import android.support.v4.view.GestureDetectorCompat;import android.support.v4.view.ViewCompat;import android.support.v4.view.animation.FastOutLinearInInterpolator;import android.util.AttributeSet;import android.util.Log;import android.view.GestureDetector;import android.view.MotionEvent;import android.view.View;import android.widget.OverScroller;import java.util.ArrayList;import java.util.List;import static com.socks.scrollerdemo.CommentBar.Direction.RIGHT;/** * Created by ${charles}     on 2017/10/18. * * @desc ${TODO} */public class CommentBar extends View{    private float xInterVal = 100f;    private GestureDetectorCompat mGestureDetector;    private int mHeight;    private int mWidth;    private float textStart;    private int paddingTop = 20;    private float startChart = 60f; //柱子开始的横坐标    private float bottomHeight = 100f;//底部横坐标高度    private float vertialInterval;//柱子之间的间隔    private float barWidth = 100f;    private float yellowBarWidth = 60;    private float grayBarWidth = 20;    private float barInterval = 10;    private Paint mLinePaint;    private Paint mVertileTextpaint;    private Paint mChartPaint;    private Paint mGrayPaint;    private Rect mBound;    private Context mContext;    private String[] vertileText = {"0","2","4","6","8","10"};    private String[] horText = {"外观","油耗","空间","舒适度","动力","操控","故障率","内饰","性价比","隔音率"};    private boolean mHorizontalFlingEnabled = true;    private OverScroller mScroller;    //滑动速度    private float mXScrollingSpeed = 1f;    private int mMinimumFlingVelocity = 0;    private int mScrollDuration = 250;    public enum Direction {        NONE, LEFT, RIGHT, VERTICAL    }    //正常滑动方向    private Direction mCurrentScrollDirection = Direction.NONE;    //快速滑动方向    private Direction mCurrentFlingDirection = Direction.NONE;    private PointF mCurrentOrigin = new PointF(0f, 0f);    private ValueAnimator mValueAnimator;    private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {        //手指按下        @Override        public boolean onDown(MotionEvent e) {//            goToNearestBar();            return true;        }        //有效的滑动        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {            switch (mCurrentScrollDirection) {                case NONE:                    // 只允许在一个方向上滑动                    if (Math.abs(distanceX) > Math.abs(distanceY)) {                        if (distanceX > 0) {                            mCurrentScrollDirection = Direction.LEFT;                        } else {                            mCurrentScrollDirection = RIGHT;                        }                    } else {                        mCurrentScrollDirection = Direction.VERTICAL;                    }                    break;                case LEFT:                    // Change direction if there was enough change.                    if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX < 0)) {                        mCurrentScrollDirection = RIGHT;                    }                    break;                case RIGHT:                    // Change direction if there was enough change.                    if (Math.abs(distanceX) > Math.abs(distanceY) && (distanceX > 0)) {                        mCurrentScrollDirection = Direction.LEFT;                    }                    break;            }            // 重新计算滑动后的起点            switch (mCurrentScrollDirection) {                case LEFT:                case RIGHT:                    mCurrentOrigin.x -= distanceX * mXScrollingSpeed;                    ViewCompat.postInvalidateOnAnimation(CommentBar.this);                    break;            }            Log.e(" mCurrentOrigin.x " , mCurrentOrigin.x + "");            return true;        }        //快速滑动        @Override        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {            if ((mCurrentFlingDirection == Direction.LEFT && !mHorizontalFlingEnabled) ||                    (mCurrentFlingDirection == RIGHT && !mHorizontalFlingEnabled)) {                return true;            }            mCurrentFlingDirection = mCurrentScrollDirection;            mScroller.forceFinished(true);            switch (mCurrentFlingDirection) {                case LEFT:                case RIGHT:                    mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, (int) (velocityX * mXScrollingSpeed), 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);                    break;                case VERTICAL:                    break;            }            ViewCompat.postInvalidateOnAnimation(CommentBar.this);            Log.e(" mCurrentOrigin.x2 " , mCurrentOrigin.x + "");            return true;        }        //单击事件        @Override        public boolean onSingleTapConfirmed(MotionEvent e) {            return super.onSingleTapConfirmed(e);        }        //长按        @Override        public void onLongPress(MotionEvent e) {            super.onLongPress(e);        }    };    @Override    public void computeScroll() {        super.computeScroll();        if (mScroller.isFinished()) {//当前滚动是否结束            if (mCurrentFlingDirection != Direction.NONE) {//                goToNearestBar();            }        } else {            if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) { //惯性滑动时保证最左边条目展示正确//                goToNearestBar();            } else if (mScroller.computeScrollOffset()) {//滑动是否结束 记录最新的滑动的点 惯性滑动处理                mCurrentOrigin.y = mScroller.getCurrY();                mCurrentOrigin.x = mScroller.getCurrX();                ViewCompat.postInvalidateOnAnimation(this);            }        }    }    /**     * Check if scrolling should be stopped.     *     * @return true if scrolling should be stopped before reaching the end of animation.     */    private boolean forceFinishScroll() {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {            return mScroller.getCurrVelocity() <= mMinimumFlingVelocity;        } else {            return false;        }    }    @Override    public boolean onTouchEvent(MotionEvent event)    {        //将view的OnTouchEvent事件交给手势监听器处理        boolean val = mGestureDetector.onTouchEvent(event);        // 正常滑动结束后 处理最左边的条目        if (event.getAction() == MotionEvent.ACTION_UP && mCurrentFlingDirection == Direction.NONE) {            if (mCurrentScrollDirection == RIGHT || mCurrentScrollDirection == Direction.LEFT) {//                goToNearestBar();            }            mCurrentScrollDirection = Direction.NONE;        }        return val;    }    public CommentBar(Context context)    {        super(context);    }    public CommentBar(Context context, AttributeSet attrs)    {        super(context, attrs);        mContext = context;        //初始化手势        initData();        mGestureDetector = new GestureDetectorCompat(context, mGestureListener);        // 解决长按屏幕后无法拖动的现象 但是 长按 用不了        mGestureDetector.setIsLongpressEnabled(false);        mScroller = new OverScroller(mContext, new FastOutLinearInInterpolator() );        textStart = startChart  + xInterVal + (barWidth / 2f);        mBound = new Rect();        mLinePaint = new Paint();        mLinePaint.setColor(Color.RED);        mLinePaint.setAntiAlias(true);        mLinePaint.setStyle(Paint.Style.FILL);        mLinePaint.setStrokeWidth(3);        mVertileTextpaint = new Paint();        mVertileTextpaint.setAntiAlias(true);        mVertileTextpaint.setColor(Color.RED);        mVertileTextpaint.setTextSize(36);        mChartPaint = new Paint();        mChartPaint.setAntiAlias(true);        mChartPaint.setColor(Color.YELLOW);        mChartPaint.setStyle(Paint.Style.FILL);        mLinePaint.setStrokeWidth(3);        mGrayPaint = new Paint();        mGrayPaint.setAntiAlias(true);        mGrayPaint.setColor(Color.GRAY);        mGrayPaint.setTextSize(36);        mValueAnimator = ValueAnimator.ofFloat(0,(getWidth()  + xInterVal - (horText.length * bottomHeight + (horText.length ) * vertialInterval + startChart)),0);        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()        {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator)            {                Log.e(" valueAnimator ","" +   valueAnimator.getAnimatedValue());                mCurrentOrigin.x = (float) valueAnimator.getAnimatedValue();                invalidate();            }        });        mValueAnimator.setDuration(2500);        mValueAnimator.start();        mValueAnimator.addListener(new Animator.AnimatorListener()        {            @Override            public void onAnimationStart(Animator animator)            {            }            @Override            public void onAnimationEnd(Animator animator)            {            }            @Override            public void onAnimationCancel(Animator animator)            {            }            @Override            public void onAnimationRepeat(Animator animator)            {            }        });    }    public CommentBar(Context context, AttributeSet attrs, int defStyleAttr)    {        super(context, attrs, defStyleAttr);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)    {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //宽度的模式        int mWidthModle = MeasureSpec.getMode(widthMeasureSpec);        //宽度大小        int mWidthSize = MeasureSpec.getSize(widthMeasureSpec);        int mHeightModle = MeasureSpec.getMode(heightMeasureSpec);        int mHeightSize = MeasureSpec.getSize(heightMeasureSpec);        //如果明确大小,直接设置大小        if (mWidthModle == MeasureSpec.EXACTLY) {            mWidth = mWidthSize;        } else {            //计算宽度,可以根据实际情况进行计算            mWidth = (getPaddingLeft() + getPaddingRight());            //如果为AT_MOST, 不允许超过默认宽度的大小            if (mWidthModle == MeasureSpec.AT_MOST) {                mWidth = Math.min(mWidth, mWidthSize);            }        }        if (mHeightModle == MeasureSpec.EXACTLY) {            mHeight = mHeightSize;        } else {            mHeight = (getPaddingTop() + getPaddingBottom());            if (mHeightModle == MeasureSpec.AT_MOST) {                mHeight = Math.min(mHeight, mHeightSize);            }        }        //设置测量完成的宽高        setMeasuredDimension(mWidth, mHeight);    }    @Override    protected void onDraw(Canvas canvas)    {        super.onDraw(canvas);        //控制图表滑动左右边界        if (mCurrentOrigin.x < getWidth() + xInterVal - (horText.length * bottomHeight + (horText.length ) * vertialInterval + startChart) )            mCurrentOrigin.x = getWidth()  + xInterVal - (horText.length * bottomHeight + (horText.length ) * vertialInterval + startChart) ;        Log.e(" ---- ",(horText.length * bottomHeight + (horText.length - 1) * vertialInterval + startChart) + "   getWidth() " +  getWidth());        if (mCurrentOrigin.x > 0)            mCurrentOrigin.x = 0;        float textHeight = mHeight + paddingTop - bottomHeight;//横坐标高度        vertialInterval = (textHeight - 200f) / 5;        canvas.drawLine(startChart,0,startChart,textHeight,mLinePaint);        canvas.drawLine(startChart,textHeight,mWidth,textHeight,mLinePaint);        for (int i=0; i < vertileText.length; i++){            float y =  (vertialInterval*i);            mVertileTextpaint.getTextBounds(vertileText[i],0,vertileText[i].length(),mBound);            canvas.drawText(vertileText[i],0,textHeight - y  + mBound.height() / 2,mVertileTextpaint);            if (i > 0)            canvas.drawLine(startChart,textHeight - y,startChart + 10,textHeight - y,mVertileTextpaint);        }        canvas.clipRect(startChart + 10, 0, mWidth, getHeight(), Region.Op.REPLACE);        float chartTempStart = startChart + xInterVal;        float maxY = textHeight - 200f;        for (int i=0; i < horText.length; i++){            float height =  200f +  (10f - barDatas.get(i).getNum()) / 10f * maxY;            String num = String.valueOf(barDatas.get(i).getNum());            mVertileTextpaint.getTextBounds(num,0,num.length(),mBound);            canvas.drawText(num,chartTempStart + mCurrentOrigin.x,height - mBound.height(),mVertileTextpaint);            RectF rectF = new RectF();            rectF.left = chartTempStart + mCurrentOrigin.x;            rectF.top = height;            rectF.right = chartTempStart + yellowBarWidth + mCurrentOrigin.x;            rectF.bottom = textHeight - 3;            canvas.drawRect(rectF,mChartPaint);            chartTempStart += ( barWidth + startChart);        }        float grayBarStart = startChart + xInterVal ;        for (int i=0; i < horText.length; i++){            float height =  200f +  (10f - avgDatas.get(i).getNum()) / 10f * maxY;            RectF rectF = new RectF();            rectF.left = grayBarStart  + (yellowBarWidth + barInterval) + mCurrentOrigin.x;            rectF.top = height;            rectF.right = grayBarStart +  (yellowBarWidth + barInterval) + grayBarWidth + mCurrentOrigin.x;            rectF.bottom = textHeight - 3;            canvas.drawRect(rectF,mGrayPaint);            grayBarStart += (barWidth + startChart );        }        float textTempStart = textStart;        for (int i=0; i < horText.length; i++){            mVertileTextpaint.getTextBounds(horText[i],0,horText[i].length(),mBound);            canvas.drawText(horText[i],textTempStart - mBound.width()/2 + mCurrentOrigin.x,mHeight - bottomHeight + mBound.height() + 38f,mVertileTextpaint);            textTempStart += (bottomHeight + startChart);        }    }    /**private void goToNearestBar() {        //让最左边的条目 显示出来        double leftBar = mCurrentOrigin.x / (bottomHeight + startChart);        if (mCurrentFlingDirection != Direction.NONE) {            // 跳到最近一个bar            leftBar = Math.round(leftBar);        } else if (mCurrentScrollDirection == Direction.LEFT) {            // 跳到上一个bar            leftBar = Math.floor(leftBar);        } else if (mCurrentScrollDirection == RIGHT) {            // 跳到下一个bar            leftBar = Math.ceil(leftBar);        } else {            // 跳到最近一个bar            leftBar = Math.round(leftBar);        }        int nearestOrigin = (int) (mCurrentOrigin.x - leftBar * (bottomHeight + startChart));        if (nearestOrigin != 0) {            // 停止当前动画            mScroller.forceFinished(true);            //开始滚动            mScroller.startScroll((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, -nearestOrigin, 0, (int) (Math.abs(nearestOrigin) / (bottomHeight + startChart) * mScrollDuration));            ViewCompat.postInvalidateOnAnimation(CommentBar.this);        }        //重新设置滚动方向.        mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE;    }*/    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        mWidth = getWidth();        mHeight = getHeight() - paddingTop;    }    private List<BarData> barDatas = new ArrayList<>();    private List<BarData> avgDatas = new ArrayList<>();    private void initData(){        barDatas.add(new BarData("外观",9.3f));        barDatas.add(new BarData("油耗",8.1f));        barDatas.add(new BarData("空间",9.5f));        barDatas.add(new BarData("舒适度",8.8f));        barDatas.add(new BarData("动力",8.5f));        barDatas.add(new BarData("操控",8.6f));        barDatas.add(new BarData("故障率",0.7f));        barDatas.add(new BarData("内饰",8.6f));        barDatas.add(new BarData("性价比",8.3f));        barDatas.add(new BarData("隔音率",0.7f));        avgDatas.add(new BarData("外观",9.1f));        avgDatas.add(new BarData("油耗",7.7f));        avgDatas.add(new BarData("空间",8.8f));        avgDatas.add(new BarData("舒适度",8.6f));        avgDatas.add(new BarData("动力",8.6f));        avgDatas.add(new BarData("操控",8.6f));        avgDatas.add(new BarData("故障率",2.6f));        avgDatas.add(new BarData("内饰",8.5f));        avgDatas.add(new BarData("性价比",8.2f));        avgDatas.add(new BarData("隔音率",2.5f));    }}


原创粉丝点击