自定义View:画布实现自定义View(折线图的实现)
来源:互联网 发布:遇见聊天软件 编辑:程序博客网 时间:2024/06/01 11:30
今天道长打算说一下用画布实现自定义View,这是道长说的自定义View的第四种实现方式了。
第一种:是放好布局后使用NineOldAndroid监听动画实现,想看一下的话点击传送门属性动画(二):如何自定义View以及自定义View:侧滑菜单动画实现。
第二种:是放好布局后使用TouchEvent监听实现,传送门在此自定义View:侧滑菜单实现。
第三种:是继承相关的View,拓展相关View的功能,传送门在此PopWindow:基本使用与自定义PopWindow。
第三种自定义View就是拓展相关View的功能。比如自定义PopWindow要增加出现动画或者展示方式。前两种都是使用已经存在的布局,一种继承FrameLayout,另一种继承ViewGroup。放置好位置后监听事件实现。应该说前两种是自定义组合View。今天说的这种方式继承View,可以用画布绘制各种形状的图形,然后监听事件实现。这里以折线图的实现为例,折线图可以左右滑动。好了咱们开车……
一、效果图
动态图没有,先把效果图放在这里,然后绘制View。
二、绘制View
上面的效果图都看到折线图有网格,有横向限制区域,有标记点,有目标点,有坐标轴单位,Y轴分割为两个区域,还可以左右滑动。
把画布Canvas与生活中的纸张看成一样就可以了,要知道咱们在纸张上写东西时先写的会被后写的遮盖住。所以说绘制View时要注意分层。
- 构造函数,初始化画布,画笔
public CanvasView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public CanvasView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CanvasView(Context context) { this(context, null); }private void init(Context context) { mTextColorSize = sp2px(context, mTextColorSize); mTextColorSmall = sp2px(context, mTextColorSmall); mTrendLineSize = dp2px(context, mTrendLineSize); mInnerCircleSize = (int) dp2px(context, mInnerCircleSize); mOuterCircleSize = (int) dp2px(context, mOuterCircleSize); mOuterCircleRadius = (int) dp2px(context, mOuterCircleRadius); mInnerCircleRadius = (int) dp2px(context, mInnerCircleRadius); mYCenterSize = (int) dp2px(context, mYCenterSize); focusTextSize = (int) dp2px(context, focusTextSize); mPaint = new Paint(); mPaint.setTextAlign(Align.CENTER); mPaint.setStyle(Style.STROKE); mPaint.setAntiAlias(true); mInnerCirclePaint = new Paint(); mInnerCirclePaint.setTextAlign(Align.CENTER); mInnerCirclePaint.setColor(mInnerCircleColor); mInnerCirclePaint.setTextSize(mInnerCircleSize); mInnerCirclePaint.setAntiAlias(true); mInnerCirclePaint.setTextSize(mInnerCircleSize); mOuterCirclePaint = new Paint(); mOuterCirclePaint.setTextAlign(Align.CENTER); mOuterCirclePaint.setTextSize(mOuterCircleSize); mOuterCirclePaint.setAntiAlias(true); mOuterCirclePaint.setTextSize(mOuterCircleSize); mTitlePaint = new Paint(); mTitlePaint.setTextAlign(Align.CENTER); mTitlePaint.setTextSize(sp2px(context, 20)); mTitlePaint.setTextAlign(Align.CENTER); mRangeTrendBackgroundPaint = new Paint(); nRangeTrendBackgroundPaint = new Paint(); mPulsePaint = new Paint(); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); mYTitleRect = new Rect(); nYTitleRect = new Rect(); pYTitleRect = new Rect(); mPointColors = new int[]{0xFF349800, 0xFF0082b4}; // 画笔的颜色 mYTitleWidth = (int) dp2px(context, mYTitleWidth); mRangeTrendColors = new int[]{0XFFDBF9CC, 0XFFDBF9CC, 0XFFDBF9CC}; nRangeTrendColors = new int[]{0XFFE0F6FF, 0XFFE0F6FF, 0XFFE0F6FF, 0XFFE0F6FF}; mPulseColors = new int[]{0XFFFFFBE4, 0XFFFFFBE4, 0XFFFFFBE4}; }
- 第一层绘制折线图限制区域
/** * 设置界限的区域的 * * @param canvas * @param paint * @param rangeTrendColors * @param up * @param down */ private void drawBackground(Canvas canvas, Paint paint, int[] rangeTrendColors, float up, float down) { Map<String, Object> params = getViewParams(); Rect rect = new Rect(); rect.set((int) ((Float) params.get("scrollX") + 0), (int) ((Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") * up), (int) ((Float) params.get("scrollX") + getWidth()), (int) (getHeight() - (Integer) params.get("xAxisTitleHeight") - (Integer) params.get("trendHeight") * down)); LinearGradient gradient = new LinearGradient((Float) params.get("scrollX") + getWidth(), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") * up, (Float) params.get("scrollX") + getWidth(), getHeight() - (Integer) params.get("xAxisTitleHeight") - (Integer) params.get("trendHeight") * down, rangeTrendColors, null, Shader.TileMode.CLAMP); paint.setShader(gradient); canvas.drawRect(rect, paint); canvas.save(); }
效果图如下:
- 第二层绘制表格
/** * 绘制表格 * * @param canvas * @param paint */ private void drawForm(Canvas canvas, Paint paint) { for (int i = 0; i < mDrawCount; i++) { drawColumnLine(canvas, paint, 0xffd5edff, (int) mTextColorSize, i); } for (int i = 1; i <= mDrawCount + 1; i++) { if (i == 1 || i == 5 || i == 6 || i == 8) { drawRowLine(canvas, paint, 0xff7ecef9, 1, i); } else { drawRowLine(canvas, paint, 0xffe5e5e5, 1, i); } } } /** * 绘制表格竖线 * * @param canvas * @param paint * @param lineColor * @param lineWith * @param position */ private void drawColumnLine(Canvas canvas, Paint paint, int lineColor, int lineWith, int position) { Map<String, Object> params = getViewParams(); paint.setColor(lineColor); paint.setTextSize(lineWith); canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight"), (Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1), paint); } /** * 绘制表格横线 * * @param canvas * @param paint * @param lineColor * @param lineWith * @param position */ private void drawRowLine(Canvas canvas, Paint paint, int lineColor, int lineWith, int position) { Map<String, Object> params = getViewParams(); paint.setColor(lineColor); paint.setStrokeWidth(lineWith); canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth - 20, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (position - 1), (Float) params.get("scrollX") + getWidth(), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (position - 1), paint); }
效果图如下:
- 第三层绘制网格分割区域
/** * 分割网格 * * @param canvas */ private void setSplitForm(Canvas canvas, Paint paint, int color, int width) { Map<String, Object> params = getViewParams(); paint.setColor(color); paint.setStrokeWidth(width); canvas.drawRect((Float) params.get("scrollX") + 0, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (5 - 1) + 1, (Float) params.get("scrollX") + getWidth(), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (6 - 1), paint);// 长方形 }
- 第四层绘制中心标记
/** * 绘制中心标志 * * @param canvas * @param paint * @param position */ private void drawCenterSign(Canvas canvas, Paint paint, int position) { drawCenterLine(canvas, paint, position); drawTriangle(canvas, paint, position); } /** * 绘制中心线 * * @param canvas * @param paint * @param position */ private void drawCenterLine(Canvas canvas, Paint paint, int position) { Map<String, Object> params = getViewParams(); paint.setColor(mCenterColor); // 修改中心竖线颜色 paint.setStrokeWidth(mYCenterSize); canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight"), (Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1) - 20, paint); } /** * 画三角形 * * @param canvas * @param paint * @param position */ public void drawTriangle(Canvas canvas, Paint paint, int position) { Map<String, Object> params = getViewParams(); paint.setStyle(Style.STROKE); paint.setStrokeWidth(2); paint.setColor(0xff7ecef9); Path path = new Path(); path.reset(); path.moveTo((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1) - 20);// 开始坐标 也就是三角形的顶点 path.lineTo((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) - 20, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1)); path.lineTo((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) + 20, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1)); path.close(); canvas.drawPath(path, paint); // 去掉底边 mTitlePaint.setColor(Color.WHITE); mTitlePaint.setStrokeWidth(3); canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) - 19, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1), (Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) + 19, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1), mTitlePaint); }
效果图如下:
- 第五层绘制折线
/** * 绘制转折线 * * @param canvas * @param paint * @param canvasLine * @param color */ private void drawCanvasLine(Canvas canvas, Paint paint, List<Integer> canvasLine, int color) { Path mPath = new Path(); // 绘制趋势图对于的Path对象 ArrayList<Integer> LinePosition = dealCanvasData(canvasLine); Map<String, Object> params = getViewParams(); int startPosition = LinePosition.get(0); int endPosition = LinePosition.get(1); paint.setColor(color); // draw trend if (endPosition > startPosition && endPosition > 0) { for (int i = startPosition; i < endPosition; i++) { int currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - canvasLine.get(i)) / mScaleValue * ((Integer) params.get("trendHeight") / 8)); // 处理为负的数据,不需要可以屏蔽 if (canvasLine.get(i) < 0) { double Y = canvasLine.get(i) * 32.0 / 40.0; currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - Y) / scaleValue * ((Integer) params.get("trendHeight") / 8)); } if (i == startPosition) { mPath.moveTo(i * mDistance, currentY); } else { mPath.lineTo(i * mDistance, currentY); } } } canvas.drawPath(mPath, paint); canvas.save(); mPath.reset(); }
效果图如下:
- 第六层绘制圆点
/** * 绘制圆 * * @param canvas * @param paint * @param canvasLine * @param color * @param condition */ private void drawCircles(Canvas canvas, Paint paint, List<Integer> canvasLine, int color, ArrayList<Integer> condition) { ArrayList<Integer> LinePosition = dealCanvasData(canvasLine); int startPosition = LinePosition.get(0); int endPosition = LinePosition.get(1); Map<String, Object> params = getViewParams(); mOuterCirclePaint.setStrokeWidth(mTrendLineSize); mInnerCirclePaint.setStrokeWidth(mTrendLineSize); mOuterCirclePaint.setColor(color); if (endPosition > startPosition && endPosition > 0) { for (int i = startPosition; i < endPosition; i++) { int currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - canvasLine.get(i)) / mScaleValue * ((Integer) params.get("trendHeight") / 8)); // 处理为负的数据,不需要可以屏蔽 if (canvasLine.get(i) < 0) { double Y = canvasLine.get(i) * 32.0 / 40.0; currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - Y) / scaleValue * ((Integer) params.get("trendHeight") / 8)); } if (canvasLine.get(i) > condition.get(1) || canvasLine.get(i) < condition.get(0)) { drawCircle(canvas, i * mDistance, currentY); // 实心 } else { // 下面需要对 90~140的数据处理 drawCirque(canvas, i * mDistance, currentY); // 空心 } } } } /** * 绘制实心圆 * * @param canvas * @param positionX * @param positionY */ private void drawCircle(Canvas canvas, int positionX, int positionY) { canvas.drawCircle(positionX, positionY, mOuterCircleRadius, mOuterCirclePaint); } /** * 绘制空心圆 * * @param canvas * @param positionX * @param positionY */ private void drawCirque(Canvas canvas, int positionX, int positionY) { canvas.drawCircle(positionX, positionY, mOuterCircleRadius, mOuterCirclePaint); canvas.drawCircle(positionX, positionY, mInnerCircleRadius, mInnerCirclePaint); }
效果图如下:
- 第七层绘制X轴Title文字
/** * 绘制x轴Title文字 * * @param canvas * @param paint * @param data */ private void drawXTitle(Canvas canvas, Paint paint, List<String[]> data) { paint.setColor(0xff888888); paint.setTextSize(mTextColorSize); paint.setStyle(Style.FILL); List<Integer> maxItem = getMaxItem(); Map<String, Object> params = getViewParams(); int startPosition = ((Integer) params.get("firstPosition") - (Integer) params.get("offsetCount")) >= 0 ? ((Integer) params.get("firstPosition") - (Integer) params.get("offsetCount")) : 0; int endPosition = maxItem.size(); if (endPosition > startPosition && endPosition > 0) { float textBaseY_x_up = (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1) + 40; for (int i = startPosition; i < endPosition; i++) { drawCenterTextColor(i); // draw x axis up mYTitleRect.set(mDistance * (i - 1), (getHeight() - (Integer) params.get("xAxisHeight_up") - (Integer) params.get("xAxisHeight_blow") - 10), mDistance * (i + 1), getHeight() - (Integer) params.get("xAxisHeight_blow") - 10); canvas.drawText(data.get(i)[0].toString(), mYTitleRect.centerX(), textBaseY_x_up, paint); } } }
- 第八层绘制Y轴Title文字
/** * 绘制Y轴Title文字 * * @param canvas * @param paint * @param backgroundColor * @param textColor */ private void drawYTitle(Canvas canvas, Paint paint, int backgroundColor, int textColor) { Map<String, Object> params = getViewParams(); FontMetrics fontMetrics = mPaint.getFontMetrics(); float fontHeight = fontMetrics.bottom - fontMetrics.top; // 由于折线图是左右贯通的,Y轴Title在画布上会造成显示混乱,所以添加底部遮挡 paint.setColor(backgroundColor); paint.setStyle(Style.FILL); mYTitleRect.set((int) ((Float) params.get("scrollX") + 0), 0, (int) ((Float) params.get("scrollX") + mYTitleWidth) - 30, getHeight() - 80); canvas.drawRect(mYTitleRect, paint); // and y-axis values paint.setColor(textColor); paint.setTextSize(mTextColorSize); //绘制Y轴值 for (int i = 0; i <= 8; i++) { String showTitle; if (i >= 6 && i <= 8) { showTitle = 40 * (6 - i) + 120 + ""; } else { showTitle = 40 + (5 - i) * 35 + ""; } float textBaseY = ((Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (i - 1)) * 2 - (((Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (i - 1)) * 2 - fontHeight) / 2 - fontMetrics.bottom; canvas.drawText(showTitle, ((Float) params.get("scrollX") + mYTitleWidth / 2) - 20, textBaseY, paint); } }
- 在onDraw中添加绘制View代码
@Override protected void onDraw(Canvas canvas) { mDistance = (getWidth() - mYTitleWidth) / mDrawCount; currentCenter = (getWidth() - mDistance); if (mCenterPosition == -1) { int positionLocal = mCenterRecorded * mDistance; scrollTo(positionLocal - currentCenter, 0); // 根据可显示的区域 动态计算中点 mCenterPosition = 0; } // 设置字体、笔画宽度// mTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);// mTitlePaint.setStrokeWidth(4); // draw trend background // 设置dataOne界限的区域 drawBackground(canvas, mRangeTrendBackgroundPaint, mRangeTrendColors, RatioUp, RatioDown); // 设置dataTwo界限的区域 drawBackground(canvas, nRangeTrendBackgroundPaint, nRangeTrendColors, colorUp, colorDown); // 设置dataThree界限的区域 drawBackground(canvas, mPulsePaint, mPulseColors, mPulseUp, mPulseDown); // draw form drawForm(canvas, mTitlePaint); // split form setSplitForm(canvas, mTitlePaint, Color.WHITE, 2); // draw sign drawCenterSign(canvas, mTitlePaint, 6); if (mPoints != null) { // draw canvas line drawCanvasLine(canvas, mPaint, mPoints[0], mPointColors[0]); drawCanvasLine(canvas, mPaint, mPoints[1], mPointColors[1]); // draw circles ArrayList<Integer> conditionsOne = new ArrayList<>(); conditionsOne.add(90); conditionsOne.add(140); ArrayList<Integer> conditionsTwo = new ArrayList<>(); conditionsTwo.add(60); conditionsTwo.add(90); drawCircles(canvas, mPaint, mPoints[0], mPointColors[0], conditionsOne); drawCircles(canvas, mPaint, mPoints[1], mPointColors[1], conditionsTwo); } if (mData != null) { // draw canvas line drawCanvasLine(canvas, mPaint, mData[0], mPulseColor); // draw circles ArrayList<Integer> conditionsThree = new ArrayList<>(); conditionsThree.add(-70); conditionsThree.add(-10); drawCircles(canvas, mPaint, mData[0], mPulseColor, conditionsThree); } //and x-axis values if (mXAxisValues != null && mXAxisValues.size() > 0) { drawXTitle(canvas, mTitlePaint, mXAxisValues); } // draw y r xis rect drawYTitle(canvas, mTitlePaint, Color.WHITE, 0xff888888); }
效果图如下:
现在咱们的界面绘制完成,要记住顺序,不然会遮挡。然后咱们实现监听。
二、监听事件,实现逻辑
- 实现onTouchEvent监听事件逻辑
@Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: oldX = (int) event.getX(); if ((mIsBeingDragged)) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } invalidate(); mActivePointerId = event.getPointerId(0); return true; case MotionEvent.ACTION_MOVE: final int activePointerIndex = event.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { break; } final int x = (int) event.getX(activePointerIndex); int deltaX = oldX - x; if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } mIsBeingDragged = true; if (deltaX > 0) { deltaX -= mTouchSlop; } else { deltaX += mTouchSlop; } } // HorizontalScrollView if (mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) { oldX = x; mTowards = deltaX; scrollBy(deltaX, 0); invalidate(); if (mPCenterListener != null) { int nextCenter = getToNextCenter(); mPCenterListener.passCenter(nextCenter); } } invalidate(); return true; default: if (mIsBeingDragged) { mIsBeingDragged = false; int nextCenter = getToNextCenter(); mTowards = 0; int halfWidth = currentCenter; int positionLocal = nextCenter * mDistance; scrollTo(positionLocal - halfWidth, 0); if (mAEndListener != null) { mAEndListener.actionEnd(nextCenter); invalidate(); centerPosition = nextCenter; } } else { } invalidate(); return true; } return super.onTouchEvent(event); }
在监听事件中不只有左右滑动监听,还添加了滑动过中心的监听。这里道长不多说了,这篇博客还是这么干巴巴的,以后道长可能只贴代码了[手动滑稽]。有不明白的地方看Demo。现在自定义View,自定义组合View道长都说了,怎么会不说一下自定义属性,这个在自定义View中也是很常用的。咱们在开一篇博客,画布实现自定义View暂时到这里,希望这篇博客能为你提供一些帮助。
源码下载
CanvasDemo
- 自定义View:画布实现自定义View(折线图的实现)
- Android 自定义View,实现折线图
- Android 自定义View,实现折线图
- 自定义View实现天气折线图效果
- WeatherView -- 自定义View实现天气折线图
- 自定义view实现画折线图
- Android自定义View实现简单的折线图、柱状图
- 自定义View--折线图
- 自定义View折线图
- 自定义View折线图
- android自定义View,实现折线图(一)
- android自定义View,实现折线图(二)
- 安卓自定义View实现简单折线图
- 自定义View的实现
- 实现自定义的View
- 自定义View的实现
- 折线图 自定义折线图 自定义view
- Android 自定义View -- 简约的折线图
- node中Express的use深入理解
- html里面选中a标签有边框出现
- Distribute Candies
- 自定义seekBar设置字体的大小
- 【Eclipse】 Eclipse常用快捷键
- 自定义View:画布实现自定义View(折线图的实现)
- 如何在 iPhone 中实现图片的毛玻璃效果
- beyond comepare点击对比不对比文件,不弹框
- 前16名Java实用工具类
- Android 单线程也能极速刷新并获取局域网设备(IP+MAC)信息, 从4分30S 优化到 0.150S 不是梦
- 腾讯云 centos7 安装FTP
- 《ReactNative》之ListView上拉下拉刷新
- 关于启动页面只跳转一次
- H5游戏的开发特性及用户体验