自定义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)); }}
阅读全文
1 0
- 自定义view-车型评分统计图
- 自定义View --- 柱状统计图
- 自定义view-统计图
- 自定义view-绘制统计图
- Android自定义View------柱状统计图
- 自定义view之扇形统计图
- Android自定义View4——统计图View
- Android自定义View——折线统计图
- Android自定义控件--评分星级View
- 《老罗Android第二季》VideoView、自定义view、实现统计图
- Android自定义控件4——统计图View
- Android自定义View——彩色圆环统计图
- 【安卓知识点汇总】自定义View篇:实现统计图
- 自定义View实现 android圆形统计图及百分比显示
- Android自定义View实现商品评价星星评分控件
- 自定义评分
- 自定义条形对比统计图
- 自定义折线统计图
- 分类和回归(三)-逻辑回归
- 将select改造成搜索框(chosen.jquery.js)使用
- 第一章、spring概述
- java 计算文件的哈希值
- CF 631D KMP运用
- 自定义view-车型评分统计图
- android开发MediaPlayer遇到的问题记录
- cuda 核函数中的参数说明<<<Dg, Db, Ns, S>>>
- 使用laravel5.5
- 阿里云安装mysql详细过程
- mysql 5.6_38 linux 安装
- STM32平台下对外部中断梳理
- 打印1到10^n的值
- Java 7中HashMap源码解析