首页 文章 精选 留言 我的

精选列表

搜索[人设自定义],共10000篇文章
优秀的个人博客,低调大师

Android 进阶自定义View(5)图表统计PieChartView圆饼图的实现

今天讲图表统计中比较常用的一个,像支付宝的月账单啥的,都是用圆饼图来做数据统计的,先看一下我最终实现的效果图: image.png 该效果实际上是两个实心圆叠加后的效果。 image.png image.png 《一》View实现思路分析: (1)根据占比集合数据,计算所需绘制的角度,动态设置画笔颜色,drawArc()绘制外圆弧 (2)drawCircle()绘制内圆 (3)确定每块圆饼的小白点的位置 (4)绘制白点的沿线和占比文字 《二》具体实现: (1)绘制不同颜色的圆饼 for (int i = 0; i < mRateList.size(); i++) { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mColorList.get(i)); // Log.e("TAG", "startAngle=" + startAngle + "--sweepAngle=" + ((int) (mRateList.get(i) * (360)) - offset)); canvas.drawArc(rectF, startAngle, (int) (mRateList.get(i) * (360)) , true, mPaint); startAngle = startAngle + (int) (mRateList.get(i) * 360); } (2)绘制内圆 mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_081638)); canvas.drawCircle(radius + centerPointRadius + (xOffset + yOffset + textRect.width()), radius + centerPointRadius + (xOffset + yOffset + textRect.height()), radius / 1.5f, mPaint); (3)确定每块圆饼的小白点的位置,通过每段圆饼的起始角度确定该段圆弧的中心点位置。 private void dealPoint(RectF rectF, float startAngle, float endAngle, List<Point> pointList) { Path path = new Path(); //通过Path类画一个90度(180—270)的内切圆弧路径 path.addArc(rectF, startAngle, endAngle); PathMeasure measure = new PathMeasure(path, false); // Log.e("路径的测量长度:", "" + measure.getLength()); float[] coords = new float[]{0f, 0f}; //利用PathMeasure分别测量出各个点的坐标值coords int divisor = 1; measure.getPosTan(measure.getLength() / divisor, coords, null); // Log.e("coords:", "x轴:" + coords[0] + " -- y轴:" + coords[1]); float x = coords[0]; float y = coords[1]; Point point = new Point(Math.round(x), Math.round(y)); pointList.add(point); } (4)绘制以白点为起点的折线和占比文字。有个细节需要注意一下,绘制折线和比例文字时,每部分沿线和文字的绘制规则不一样,我是按下面的规则处理的:将圆分为四部分,每块区分显示。 image.png //折线横向长度 private int xOffset; //折线偏Y方向长度 private int yOffset; private void dealRateText(Canvas canvas, Point point, int position, List<Point> pointList) { if (position == 0) { lastPoint = pointList.get(0); } else { lastPoint = pointList.get(position - 1); } float[] floats = new float[8]; floats[0] = point.x; floats[1] = point.y; //右半圆 if (point.x >= radius + centerPointRadius + (xOffset + yOffset + textRect.width())) { mPaint.setTextAlign(Paint.Align.LEFT); floats[6] = point.x + xOffset; if (point.y <= radius + centerPointRadius + (xOffset + yOffset + textRect.height())) { //右上角 floats[2] = point.x + yOffset; floats[3] = point.y - yOffset; floats[4] = point.x + yOffset; floats[5] = point.y - yOffset; floats[7] = point.y - yOffset; } else { //右下角 floats[2] = point.x + yOffset; floats[3] = point.y + yOffset; floats[4] = point.x + yOffset; floats[5] = point.y + yOffset; floats[7] = point.y + yOffset; } //左半圆 } else { mPaint.setTextAlign(Paint.Align.RIGHT); floats[6] = point.x - xOffset; //防止相邻的圆饼绘制的文字重叠显示 if (point.y <= radius + centerPointRadius) { //左上角 floats[2] = point.x - yOffset; floats[3] = point.y - yOffset; floats[4] = point.x - yOffset; floats[5] = point.y - yOffset; floats[7] = point.y - yOffset; } else { //左下角 floats[2] = point.x - yOffset; floats[3] = point.y + yOffset; floats[4] = point.x - yOffset; floats[5] = point.y + yOffset; floats[7] = point.y + yOffset; } } //根据每块的颜色,绘制对应颜色的折线 // mPaint.setColor(mRes.getColor(colorList.get(position))); mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_b69b4f)); //画圆饼图每块边上的折线 canvas.drawLines(floats, mPaint); mPaint.setStyle(Paint.Style.STROKE); //绘制显示的文字,需要根据类型显示不同的文字 if (mRateList.size() > 0) { //Y轴:+ textRect.height() / 2 ,相对沿线居中显示 canvas.drawText(getFormatPercentRate(mRateList.get(position) * 100) + "%", floats[6], floats[7] + textRect.height() / 2, mPaint); } } 完整代码: package com.example.jojo.learn.customview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import com.example.jojo.learn.R; import com.example.jojo.learn.utils.DP2PX; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; /** * Created by JoJo on 2018/8/6. * wechat:18510829974 * description: 饼状图 */ public class PieView extends View { private Context mContext; private Paint mPaint; //每块占比的绘制的颜色 private List<Integer> mColorList = new ArrayList<>(); //圆弧占比的集合 private List<Float> mRateList = new ArrayList<>(); //是否展示文字 private boolean isShowRateText; //圆弧半径 private float radius; private int startAngle = 0; //不同色块之间是否需要空隙offset private int offset = 0; //圆弧中心点小圆点的圆心半径 private int centerPointRadius; private float showRateSize; private Rect textRect; //折线横向长度 private int xOffset; //折线偏Y方向长度 private int yOffset; private float mChangeAngle; private boolean isAnimation; private int sign = 0; public PieView(Context context) { this(context, null); } public PieView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; init(); initData(); } private void initData() { float[] rate = {30f, 40f, 15f, 15f}; int[] colors = {Color.RED, Color.BLUE, Color.YELLOW, Color.GRAY}; for (int i = 0; i < rate.length; i++) { mRateList.add(rate[i] / 100); mColorList.add(colors[i]); } textRect = new Rect(); if (mRateList.size() > 0) { mPaint.getTextBounds((mRateList.get(0) + "%"), 0, (mRateList.get(0) + "%").length(), textRect); } } private void init() { radius = DP2PX.dip2px(mContext, 80); centerPointRadius = DP2PX.dip2px(mContext, 2); xOffset = DP2PX.dip2px(mContext, 20); yOffset = DP2PX.dip2px(mContext, 5); showRateSize = DP2PX.dip2px(mContext, 10); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setTextSize(showRateSize); if (mRateList.size() > 0) { textRect = new Rect(); mPaint.getTextBounds((mRateList.get(0) + "%"), 0, (mRateList.get(0) + "%").length(), textRect); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (heightMode == MeasureSpec.AT_MOST) { //边沿线和文字所占的长度:(xOffset + yOffset + textRect.width()) heightSize = (int) (radius * 2) + 2 * centerPointRadius + getPaddingLeft() + getPaddingRight() + (xOffset + yOffset + textRect.height()) * 2; } if (widthMode == MeasureSpec.AT_MOST) { widthSize = (int) (radius * 2) + 2 * centerPointRadius + getPaddingLeft() + getPaddingRight() + (xOffset + yOffset + textRect.width()) * 2; } //保存测量结果 setMeasuredDimension(widthSize, heightSize); } private int paintPosition; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //(1)绘制圆饼 RectF rectF = new RectF(0 + centerPointRadius + (xOffset + yOffset + textRect.width()), 0 + centerPointRadius + (xOffset + yOffset + textRect.height()), 2 * radius + centerPointRadius + (xOffset + yOffset + textRect.width()), 2 * radius + centerPointRadius + (xOffset + yOffset + textRect.height())); List<Point> mPointList = new ArrayList<>(); for (int i = 0; i < mRateList.size(); i++) { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mColorList.get(i)); // Log.e("TAG", "startAngle=" + startAngle + "--sweepAngle=" + ((int) (mRateList.get(i) * (360)) - offset)); canvas.drawArc(rectF, startAngle, (int) (mRateList.get(i) * (360)) - offset, true, mPaint); //(2)处理每块圆饼弧的中心点,绘制折线,显示对应的文字 if (isShowRateText) { dealPoint(rectF, startAngle, (mRateList.get(i) * 360 - offset) / 2, mPointList); Point point = mPointList.get(i); mPaint.setColor(Color.WHITE);//点的绘制的颜色 canvas.drawCircle(point.x, point.y, centerPointRadius, mPaint); dealRateText(canvas, point, i, mPointList); } startAngle = startAngle + (int) (mRateList.get(i) * 360); } //(3)绘制内部中空的圆 mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_081638)); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(radius + centerPointRadius + (xOffset + yOffset + textRect.width()), radius + centerPointRadius + (xOffset + yOffset + textRect.height()), radius / 1.5f, mPaint); } private Point lastPoint; private void dealRateText(Canvas canvas, Point point, int position, List<Point> pointList) { if (position == 0) { lastPoint = pointList.get(0); } else { lastPoint = pointList.get(position - 1); } float[] floats = new float[8]; floats[0] = point.x; floats[1] = point.y; //右半圆 if (point.x >= radius + centerPointRadius + (xOffset + yOffset + textRect.width())) { mPaint.setTextAlign(Paint.Align.LEFT); floats[6] = point.x + xOffset; //防止相邻的圆饼绘制的文字重叠显示 // if (lastPoint != null) { // int absX = Math.abs(point.x - lastPoint.x); // int absY = Math.abs(point.y - lastPoint.y); // if (absX > 0 && absX < 20 && absY > 0 && absY < 20) { // floats[6] = point.x + xOffset - textRect.width() / 2; // Log.e("TAG", "右半圆"); // } else { // floats[6] = point.x + xOffset; // } // } else { // floats[6] = point.x + xOffset; // } if (point.y <= radius + centerPointRadius + (xOffset + yOffset + textRect.height())) { //右上角 floats[2] = point.x + yOffset; floats[3] = point.y - yOffset; floats[4] = point.x + yOffset; floats[5] = point.y - yOffset; floats[7] = point.y - yOffset; } else { //右下角 floats[2] = point.x + yOffset; floats[3] = point.y + yOffset; floats[4] = point.x + yOffset; floats[5] = point.y + yOffset; floats[7] = point.y + yOffset; } //左半圆 } else { mPaint.setTextAlign(Paint.Align.RIGHT); floats[6] = point.x - xOffset; //防止相邻的圆饼绘制的文字重叠显示 // if (lastPoint != null) { // int absX = Math.abs(point.x - lastPoint.x); // int absY = Math.abs(point.y - lastPoint.y); // if (absX > 0 && absX < 20 && absY > 0 && absY < 20) { // floats[6] = point.x - xOffset - textRect.width() / 2; // Log.e("TAG", "左半圆"); // } else { // floats[6] = point.x - xOffset; // } // } else { // floats[6] = point.x - xOffset; // } if (point.y <= radius + centerPointRadius) { //左上角 floats[2] = point.x - yOffset; floats[3] = point.y - yOffset; floats[4] = point.x - yOffset; floats[5] = point.y - yOffset; floats[7] = point.y - yOffset; } else { //左下角 floats[2] = point.x - yOffset; floats[3] = point.y + yOffset; floats[4] = point.x - yOffset; floats[5] = point.y + yOffset; floats[7] = point.y + yOffset; } } //根据每块的颜色,绘制对应颜色的折线 // mPaint.setColor(mRes.getColor(colorList.get(position))); mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_b69b4f)); //画圆饼图每块边上的折线 canvas.drawLines(floats, mPaint); mPaint.setStyle(Paint.Style.STROKE); //绘制显示的文字,需要根据类型显示不同的文字 if (mRateList.size() > 0) { //Y轴:+ textRect.height() / 2 ,相对沿线居中显示 canvas.drawText(getFormatPercentRate(mRateList.get(position) * 100) + "%", floats[6], floats[7] + textRect.height() / 2, mPaint); } } private void dealPoint(RectF rectF, float startAngle, float endAngle, List<Point> pointList) { Path path = new Path(); //通过Path类画一个90度(180—270)的内切圆弧路径 path.addArc(rectF, startAngle, endAngle); PathMeasure measure = new PathMeasure(path, false); // Log.e("路径的测量长度:", "" + measure.getLength()); float[] coords = new float[]{0f, 0f}; //利用PathMeasure分别测量出各个点的坐标值coords int divisor = 1; measure.getPosTan(measure.getLength() / divisor, coords, null); // Log.e("coords:", "x轴:" + coords[0] + " -- y轴:" + coords[1]); float x = coords[0]; float y = coords[1]; Point point = new Point(Math.round(x), Math.round(y)); pointList.add(point); } public void updateDate(List<Float> rateList, List<Integer> colorList, boolean isShowRateText) { this.isShowRateText = isShowRateText; this.mRateList = rateList; this.mColorList = colorList; init(); invalidate(); } /** * 获取格式化的保留两位数的数 */ public String getFormatPercentRate(float dataValue) { DecimalFormat decimalFormat = new DecimalFormat(".00");//构造方法的字符格式这里如果小数不足2位,会以0补足. return decimalFormat.format(dataValue); } }

优秀的个人博客,低调大师

Android开发 AIDL使用自定义对象作参数或返回值

http://www.pocketdigi.com/20121129/952.html 默认,AIDL支持对象作参数,但需要该对象实现Parcelable接口,且aidl文件应该是该类在同一包下,需要单独给该类定义一个aidl文件. 定义模型类: EnglishItem.java: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 package com.pocketdigi.english.aidl; import android.os.Parcel; import android.os.Parcelable; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; import com.pocketdigi.english.utils.Constants; import com.pocketdigi.english.utils.Des; @DatabaseTable(tableName = "listenlist") public class EnglishItem implements Parcelable { /** * */ private static final long serialVersionUID = -1756522544697525757L; @DatabaseField(id = true) private int id; @DatabaseField private String title; @DatabaseField private String category; @DatabaseField private String mp3url; @DatabaseField private String lrcurl; @DatabaseField private String txturl; public static final Parcelable.Creator<EnglishItem> CREATOR = new Parcelable.Creator<EnglishItem>() { public EnglishItem createFromParcel(Parcel in) { return new EnglishItem(in); } public EnglishItem[] newArray(int size) { return new EnglishItem[size]; } }; private EnglishItem(Parcel in) { readFromParcel(in); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public String getMp3url() { return Des.decodeValue(Constants.DES_KEY, mp3url); } public void setMp3url(String mp3url) { this.mp3url = mp3url; } public String getLrcurl() { return Des.decodeValue(Constants.DES_KEY, lrcurl); } public void setLrcurl(String lrcurl) { this.lrcurl = lrcurl; } public String getTxturl() { return Des.decodeValue(Constants.DES_KEY, txturl); } public void setTxturl(String txturl) { this.txturl = txturl; } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel out, int flag) { // TODO Auto-generated method stub out.writeInt(id); out.writeString(title); out.writeString(category); out.writeString(mp3url); out.writeString(lrcurl); out.writeString(txturl); } /** * 读 * @param in */ public void readFromParcel(Parcel in) { id=in.readInt(); title=in.readString(); category=in.readString(); category=in.readString(); lrcurl=in.readString(); txturl=in.readString(); } } Parcelable.Creator不可少。 同包下定义一个EnglishItem.aidl: 1 2 package com.pocketdigi.english.aidl; parcelable EnglishItem; 最后是调用接口aidl: PlayerAidl.aidl: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.pocketdigi.english.aidl; import com.pocketdigi.english.aidl.EnglishItem; interface PlayerAidl { /** *传入列表 **/ void putList(inout List<EnglishItem> item); /** *播放指定音频 **/ void play(int index); void pause(); /** *暂停后恢复播放 **/ void resume(); /** *删除指定项 **/ void delete(int position); /** *停止服务 **/ void stopService(); } 分类: android solve 本文转自wanqi博客园博客,原文链接:http://www.cnblogs.com/wanqieddy/p/3922846.html如需转载请自行联系原作者

优秀的个人博客,低调大师

Android自定义View探索(三)—事件分发机制与滑动冲突处理

View的事件分发机制,其实就是对MotionEvent事件的分发机制,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,这个传递的过程就是分发过程。 一.View的事件分发机制 整体流程: dispatchTouchEvent()—>onTouch()—>onTouchEvent()—>onClick() dispatchTouchEvent():View事件分发的入口,返回值表示MotionEvent是否被消费。 onTouch():View需要处理事件时,如果它设置了onTouchListener,那么onTouchListener中的onTouch方法会被调用。如果onTouch方法返回false,那么View的onTouchEvent()方法会被调用;如果onTouch方法返回true,那么View的onTouchEvent()方法将不会被调用。onTouch()的优先级比onTouchEvent()的优先级更高。 onTouchEvent():View自身对于Touch处理的实现 ,返回结果表示是否消耗当前事件。 onClick() : View的onTouchEvent()的优先级比onClick()高,用于处理clickListener设置的点击事件。 二.ViewGroup的事件分发机制 整体流程: 图片来自 谷歌的小弟 当一个点击事件产生以后,它的传递过程遵循如下顺序:Activity—>Window—>外层ViewGroup。 外层ViewGroup接收到事件以后,就会按照事件分发机制去分发事件。如果这个ViewGroup的onInterceptTouchEvent()方法返回true,就表示它要拦截当前事件,接着事件就会交给这个ViewGroup来处理;如果这个ViewGroup的onInterceptTouchEvent()方法返回false,就表示它不拦截当前事件,接着事件就会交给它的子元素来处理。如果一个View的onTouchEvent()返回false,那么它的父容器的onTouchEvent()就会被调用。以此类推,如果所有的元素都不处理这个事件,那么这个事件最终会传递给Activity处理,即Activity的onTouchEvent()方法会调用。 三.相关知识点 1.同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有不定量的move事件,最终以up事件结束。 2.某个View一旦决定拦截,那么这一事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent()不会再被调用,这是因为系统会将同一事件序列内的其他方法都交给他处理。 3.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent方法返回了false),那么同一事件序列中的其他事件都不会交给它来处理,并且事件将重新交给它的父元素处理,即父元素的onTouchEvent会被调用。 4.ViewGroup默认不拦截任何事件,即默认onInterceptTouchEvent返回false。View没有onInterceptTouchEvent方法,一旦有事件传递给它,它的 onTouchEvent方法就会调用。 5.事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的分发过程,但是ACTION_DOWN事件除外。 四.滑动冲突的处理 内部拦截法:子View禁止父View拦截Touch事件 内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器处理。让子View调用requestDisallowInterceptTouchEvent( )禁止父View对Touch的拦截即可,这样就可以解决滑动冲突的问题。 外部拦截法:父View中准确地进行事件分发和拦截 外部拦截法是指点击事件都先经过父容器拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。我们可以重写父View中与Touch事件分发相关的方法—–onInterceptTouchEvent( )来进行处理,这样就可以解决滑动冲突的问题。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册