//复制粘贴就完事了
//图片和颜色值需要自己替换一下
<color name="color_EE">#EEEEEE</color>
<color name="color_100">#2BE783</color>
<color name="color_999">#999999</color>
//自定义折线图
public class LineView extends View {
Paint linePaint;//画线
//数据圆点被选中的画笔
private Paint pointSelectedPaint = new Paint();
private Paint bgPaint;//画背景线
private Paint textPaint;//画文字(值)
private Paint lastPaint;//画最后一个文字(值)
private boolean isInit=false;//初始化画笔
List<DataBean> dataBeans = new ArrayList<>();//数据集合
private int width, height;//父容器宽高
private int xW,yH;//父容器的平均宽高
//private int marginH=50,marginW=75;//上下左右边距(左右边距相等,上下边距相等,废弃了)
//上下左右边距(只用到上和左边距)
private int top_margin=50,buttom_margin=50,left_margin=75,right_margin=75;
private int marginTopButtom=100,marginLeftRight=150;//左右边距和
private int maxNum=5000;//y轴最大值
private int averageValue=1000;//平均值
private boolean isClick;//距离最近的点
private int clickIndex;//距离最近的点下标
//是否需要点击事件
private boolean clickable;//是否点击(可用于控制外部布局)
private IsVisibility isVisibility;//创造点击事件
private Bitmap bitmap;//画图片并且压缩图片,防止图片过大造成OOM
private int j=1;//记录当前天数
private int m=1;//后端返回开始的(第一个月的)月份
private int y=2021;//后端返回年份
public LineView(Context context) {
super(context);
}
public LineView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public LineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//y轴标签最大值
public void setMaxNum(int maxNum){
this.maxNum = maxNum;
}
//数据集合
public void setDataBeans(List<DataBean> dataBeans){
this.dataBeans = dataBeans;
}
//手指按下移动显示,抬起隐藏
public void isVisibility(IsVisibility isVisibility){
this.isVisibility = isVisibility;
}
public interface IsVisibility{
void isVisibility(boolean clickable,int i);
}
//年和月
public void setYear(int y,int m){
this.y = y;
this.m = m;
}
//平均值
public void setAverageValue(int averageValue){
this.averageValue = averageValue;
}
//设置上下左右边距 marginH*2=marginTopButtom,marginW*2=marginLeftRight 必须相等(需求左右上下不相等时自行调整)
public void setMargin(int top_margin,int buttom_margin,int marginTopButtom,int left_margin,int right_margin,int marginLeftRight){
this.top_margin = top_margin;
this.buttom_margin = buttom_margin;
this.marginTopButtom = marginTopButtom;
this.left_margin = left_margin;
this.right_margin = right_margin;
this.marginLeftRight = marginLeftRight;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isInit) {
init();//初始化画笔
isInit=true;
}
//画Y轴上的横线,所以只有Y轴坐标发生变化
for (int i=0;i<=maxNum/averageValue;i++){
//开始的坐标和结束的坐标,背景画笔 xW+marginW 父容器的宽平均值+宽的边距 i*yH+marginH 当前线*父容器高平均值+高的边距
canvas.drawLine(xW+left_margin,i*yH+top_margin,width+left_margin,i*yH+top_margin,bgPaint);
String num = i * averageValue + "";//y轴标签值
//计算文字的宽高(高度准确,宽度不太准确,需要另一个计算宽度的方法,在下面)
Rect rect = new Rect();
textPaint.getTextBounds(num,0,num.length(),rect);
rect.width();//文字的宽度
int textHeight = rect.height();//文字的高度
float clickBgWidth = textPaint.measureText(num);//文字的宽度
canvas.drawText(num,left_margin-clickBgWidth, height -i*yH+top_margin+textHeight/2,textPaint);//画Y轴标签值
}
yearM(canvas);
for (int i=0;i<dataBeans.size();i++){
if (i<(dataBeans.size()-1)){
//画出折线(算是折线图的内容吧) xW+i*xW+marginW 父容器的宽+当前平均值+边距
//height-dataBeans.get(i).pY*height / maxNum+marginH 父容器的高度-值*父容器的高度/最大值+高度的边距
// dataBeans.get(i).pY*height / maxNum 压缩比例值(类似于图片的二次采样)
//第二个坐标的(i+1),其他没有变化
canvas.drawLine(xW+i*xW+left_margin, height-dataBeans.get(i).pY*height / maxNum+top_margin,xW+(i+1)*xW+left_margin, height-dataBeans.get(i+1).pY*height / maxNum+top_margin, linePaint);
}
if (i==dataBeans.size()-1){
canvas.drawCircle(xW+i*xW+left_margin, height-dataBeans.get(i).pY*height / maxNum+top_margin, 5, linePaint);
String s = dataBeans.get(i).pY + "";
Rect rect = new Rect();
textPaint.getTextBounds(s,0,s.length(),rect);
rect.width();
int clickBgHeight = rect.height();
float clickBgWidth = textPaint.measureText(s);
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_click_icon);
Bitmap resizeBitmap = resizeBitmap(bitmap, clickBgWidth, clickBgHeight);
RectF rectF = new RectF();
rectF.left =xW+i*xW - clickBgWidth-100+left_margin;
rectF.top = height-dataBeans.get(i).pY*height / maxNum+top_margin-clickBgHeight-50;
rectF.right = xW+i*xW+left_margin;
rectF.bottom = height-dataBeans.get(i).pY*height / maxNum+top_margin;
canvas.drawBitmap(resizeBitmap, null, rectF, linePaint);//绘制选中后的值背景
//xW+i*xW - (clickBgWidth/2+(clickBgWidth+100)/2) 文字是在X轴的右边,X轴右边点-(所以文字宽度除以二 + 图片宽度除以二)
//height-dataBeans.get(i).pY*height / maxNum+margin+clickBgHeight/2-(clickBgHeight+50)/2 文字在Y轴的上面,y轴底部点+所以高度除以二-图片高度初二
canvas.drawText(dataBeans.get(i).pY+"",xW+i*xW - clickBgWidth-50+left_margin,height-dataBeans.get(i).pY*height / maxNum+top_margin-27,lastPaint);
}
//选中时的状态
if (isClick&&clickIndex==i&&clickable){
canvas.drawLine(xW+i*xW+left_margin, top_margin,xW+i*xW+left_margin, height+top_margin, textPaint);
//绘制外层圆环
canvas.drawCircle(xW+i*xW+left_margin, height-dataBeans.get(i).pY*height / maxNum+top_margin, 3, pointSelectedPaint);
}
}
}
private void yearM(Canvas canvas) {
//这里不能是全局变量,因为滑动会一直绘制,所以全局时m值会一直变大
int j=this.j;//记录当前天数
int m=this.m;//后端返回开始的(第一个月的)月份
int y=this.y;//后端返回年份
String h = 1+"月";//x轴标签值
Rect rectH = new Rect();
textPaint.getTextBounds(h,0,h.length(),rectH);//获取文字宽高
//画第一个月(初始化)
canvas.drawText(1+"月", xW-top_margin+textPaint.measureText(1+"月")/2+left_margin,height+top_margin+rectH.height()+15,textPaint);
for (int i=0;i<dataBeans.size();i++){
//获取文字的宽度,在坐标点的右上方,所以文字以坐标点为中心的话需要这样计算(x轴坐标-文字宽度/2,y轴坐标+文字高度/2)
float xLableWidth = textPaint.measureText((i+1)+"月");
String num = (i+1)+"月";
Rect rectM = new Rect();
textPaint.getTextBounds(num,0,num.length(),rectM);
rectM.width();//文字的宽度
int textHeight = rectM.height();//文字的高度
//计算月份(画出X轴上的月份)
if (j<getMonthLastDay(y,m)){
j++;
}else {
j=1;
if (m>=12){
m=1;
}else {
m++;
}
//月份xW+i*xW-marginH+xLableWidth/2+marginW
canvas.drawText(m+"月",xW+i*xW-top_margin+xLableWidth/2+left_margin,height+top_margin+textHeight+15,textPaint);
}
}
}
private void init() {
linePaint = new Paint();
bgPaint = new Paint();
textPaint = new Paint();
lastPaint = new Paint();
/*获取父容器的宽和高*/
width = getWidth()-marginLeftRight;
height = getHeight()-marginTopButtom;
//父容器的平均值
xW = width / dataBeans.size();
yH = height / (maxNum/averageValue);
//只是绘制的XY轴
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeWidth((float) 5.0); //设置线宽
linePaint.setColor(getResources().getColor(R.color.color_100));
linePaint.setAntiAlias(true);// 锯齿不显示
//只是绘制的XY轴
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setStrokeWidth((float) 3.0); //设置线宽
bgPaint.setColor(getResources().getColor(R.color.color_EE));
bgPaint.setAntiAlias(true);// 锯齿不显示
//只是绘制的XY轴
textPaint.setStyle(Paint.Style.FILL);
textPaint.setStrokeWidth((float) 1.0); //设置线宽
textPaint.setColor(getResources().getColor(R.color.color_999));
textPaint.setTextSize(30);
textPaint.setAntiAlias(true);// 锯齿不显示
//点击后被选中的圆
pointSelectedPaint.setAntiAlias(true);
pointSelectedPaint.setStrokeWidth(6);
pointSelectedPaint.setStyle(Paint.Style.STROKE);
pointSelectedPaint.setColor(getResources().getColor(R.color.color_999));
//点击后被选中的圆
lastPaint.setAntiAlias(true);
lastPaint.setStrokeWidth(1);
lastPaint.setTextSize(30);
lastPaint.setStyle(Paint.Style.FILL);
lastPaint.setColor(getResources().getColor(R.color.white));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN://按下
case MotionEvent.ACTION_MOVE://移动 ACTION_HOVER_MOVE 来回移动 HOVER徘徊
for (int i = 0; i < dataBeans.size(); i++) {
int dataX = xW + i * xW;
// 控制触摸/点击的范围,在有效范围内才触发
if (Math.abs(touchX - dataX) < xW / 2) {//Math.abs(touchX - dataX)绝对值
isClick = true;
clickIndex = i;
clickable=true;
if (isVisibility!=null){
isVisibility.isVisibility(true,i);
}
invalidate();
//显示点击之后的图片
}
}
break;
case MotionEvent.ACTION_UP://抬起 ACTION_POINTER_UP//再次按下前走这个抬起
clickable=false;
if (isVisibility!=null){
isVisibility.isVisibility(false,-1);
}
invalidate();
break;
}
return true;
}
/**
* 得到指定月的天数
* */
public static int getMonthLastDay(int year, int month)
{
Calendar a = Calendar.getInstance();
a.set(Calendar.YEAR, year);
a.set(Calendar.MONTH, month - 1);
a.set(Calendar.DATE, 1);//把日期设置为当月第一天
a.roll(Calendar.DATE, -1);//日期回滚一天,也就是最后一天
int maxDate = a.get(Calendar.DATE);
return maxDate;
}
/**
* 使用Matrix将Bitmap压缩到指定大小
*
* @param bitmap
* @param w
* @param h
* @return
*/
public static Bitmap resizeBitmap(Bitmap bitmap, float w, float h) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = w / width;
float scaleHeight = h / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width,
height, matrix, true);
return resizedBitmap;
}
/**
* 释放bitmap资源
*/
public void recycleBitmap() {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
//MainActivity使用
public class MainActivity extends AppCompatActivity {
private ArrayList<DataBean> dataBeans;
private RelativeLayout rlt_revenue;
private TextView tv_time;
private TextView tv_revenue;
private int j=1;
private int m=1;//后端返回开始的(第一个月的)月份
private int y=2021;//后端返回年份
private LineView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = findViewById(R.id.lv);
rlt_revenue = findViewById(R.id.rlt_revenue);
tv_time = findViewById(R.id.tv_time);
tv_revenue = findViewById(R.id.tv_revenue);
lv.setMaxNum(1000);//最大值
lv.setAverageValue(1000);//平均值
lv.setYear(2021,1);//年和月
lv.setMargin(50,50,100,100,50,150);//上下左右边距
dataBeans = new ArrayList<>();
Random random = new Random();
for (int i=0;i<90;i++){
DataBean dataBean = new DataBean();
dataBean.pY=random.nextInt(1000);//不能大于最大值
dataBean.mX=i;
if (j<getMonthLastDay(y,m)){
dataBean.time="2021-"+m+"-"+j;
j++;
}else {
//月份
dataBean.time="2021-"+m+"-"+j;
j=1;
m++;
}
dataBeans.add(dataBean);
}
lv.setDataBeans(dataBeans);
lv.isVisibility(new LineView.IsVisibility() {
@Override
public void isVisibility(boolean clickable, int i) {
Log.d("+++++++++++++++++i=",i+"");
if (clickable){
rlt_revenue.setVisibility(View.VISIBLE);
tv_time.setText(dataBeans.get(i).time);
tv_revenue.setText("+"+dataBeans.get(i).pY);
}else {
rlt_revenue.setVisibility(View.GONE);
}
}
});
}
/**
* 得到指定月的天数
* */
public static int getMonthLastDay(int year, int month)
{
Calendar a = Calendar.getInstance();
a.set(Calendar.YEAR, year);
a.set(Calendar.MONTH, month - 1);
a.set(Calendar.DATE, 1);//把日期设置为当月第一天
a.roll(Calendar.DATE, -1);//日期回滚一天,也就是最后一天
int maxDate = a.get(Calendar.DATE);
return maxDate;
}
@Override
protected void onDestroy() {
super.onDestroy();
lv.recycleBitmap();//释放bitmap资源
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.cheyifu.myapplication.LineView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="130dp"/>
<RelativeLayout
android:id="@+id/rlt_revenue"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="#D9FFEB">
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10dp"
android:textColor="#333333"
android:textStyle="bold"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:text="2021-05-26"/>
<!--Cumulative 累计-->
<TextView
android:id="@+id/tv_revenue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10dp"
android:textColor="#ff0000"
android:textStyle="bold"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
android:text="+3625.14"/>
<TextView
android:layout_toLeftOf="@+id/tv_revenue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10dp"
android:textColor="#333333"
android:textStyle="bold"
android:layout_centerVertical="true"
android:text="累计收益:"/>
</RelativeLayout>
</LinearLayout>
Android 自定义 text seekbar Android 自定义折线图
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。

提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
Android 自定义折线图实现教程
前言:各位同学大家好,有段时间没有给大家更新文章了,具体多久我也记不清楚了。最近重新复习了一下原生安卓的知识点,写了一个安卓原生自定
android android studio android-studio java i++