需求与结果
技术人信仰:没图说个卵子。
UI设计图:
实现图:
思路篇
毋庸置疑,肯定要用自定义ViewGroup+View,会有童鞋问:“为啥不直接用自定义View”,不想回答,自行思考。
首先 ,我们要先想下配置,看到图的瞬间我们就应该有个大概的思路。大致必须要拥有以下几个类:
- ChartGroup: 自定义ViewGroup负责控件排版,事件分发。
- PathView: 自定义View,底部那个xyz轴承图。
- ChartContentView: 自定义的EditText,加一些选中啊,Point属性。
- Y_ChartGroup: emmm 就是继承LinearLayout 然后控制Y小轴上的控件变化
- ChartConstant: 配置属性 最短长度,控件宽度等。
- ChartUtils: 顾名思义,不多BB.
实践篇1(ChartConstant)
首先我们要确初始化的时候需要的属性:
- PathView : 后期刷新什么的都需要这个东东,且这个必须唯一。
- Type: 就是ChartContentView的父布局,是Z轴还是Y0,1,2.....
- ChartContentWidth,Height: 就是那个输入框的宽高。
- MIN_HEIGHT: 最小高度 就是z周一段一段的最短距离。
- FILE_DIR : 文件存储路径。
- isAllowCheck: 这是后期需求变动加的,等下源码自己看作用吧。
实践篇2(PathView)
自定义View主要步骤三部曲:
构造方法;
onMeasure();
onDraw();
这个PathView 一眼看去,可知,需要这么几个属性:
- 两个Paint(线跟多边形背景) 两个TextPaint(轴名称,Y支轴名称)
- 各个轴上的ChartContentView的集合
- 各个轴的长度(用来确定View宽度)
- 点的集合(用来画背景的那个多边形)
确定了这些然后在构造方法中初始化:
setClickable(true);
MIN_HEIGHT = ChartConstant.ChartAxis_MinHeight;
CHART_VIEW_WIDTH = ChartConstant.ChartContentWidth;
Z_LENGTH = MIN_HEIGHT * 10;
y1_length = y2_length = y3_length = y4_length = Z_LENGTH;
Y_LENGTH = (int) Math.sqrt(Math.pow(Z_LENGTH, 2) * 2);
X_LENGTH = CHART_VIEW_WIDTH * 3;
AXIS_TEXT_HEIGHT = MIN_HEIGHT * 2;
//初始化画笔
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(AXIS_COLOR);
mLinePaint.setStrokeWidth(1.5f);
mBgPaint = new Paint();
mBgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mBgPaint.setAntiAlias(true);
mBgPaint.setColor(Color.parseColor("#f5caca"));
mBgPaint.setAlpha(122);
axis_textPaint = new TextPaint();
axis_textPaint.setAntiAlias(true);
axis_textPaint.setColor(AXIS_NAME_COLOR);
axis_textPaint.setFakeBoldText(true);
axis_textPaint.setTextSize(DisplayUtils.sp2px(context, 15));
axis_textpaint_y = new TextPaint();
axis_textpaint_y.setAntiAlias(true);
axis_textpaint_y.setColor(Color.parseColor("#4b4b4b"));
axis_textpaint_y.setTextSize(DisplayUtils.sp2px(context, 11));
//横向数组
y0List = new ArrayList<>();
y1List = new ArrayList<>();
y2List = new ArrayList<>();
y3List = new ArrayList<>();
y4List = new ArrayList<>();
points = new ArrayList<>();
接下来看Measure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = Z_LENGTH + AXIS_TEXT_HEIGHT + MIN_HEIGHT;//总高度
int width = X_LENGTH;
width = initViewWidth(width);
setMeasuredDimension(width, height);
}
private int initViewWidth(int width) {
for (int i = 0; i < 5; i++) {
if (width < getRealWidth(i)) {
width = getRealWidth(i);
}
}
return width;
}
private int getRealWidth(int i) {
int width = getMeasuredWidth();
switch (i) {
case 0:
width = X_LENGTH + ChartConstant.ChartContentWidth * 3;
break;
case 1:
width = y1_length + ChartConstant.ChartAxis_MinHeight * 3 + ChartConstant.ChartContentWidth * 2;
break;
case 2:
width = y2_length + ChartConstant.ChartAxis_MinHeight * 5 + ChartConstant.ChartContentWidth * 2;
break;
case 3:
width = y3_length + ChartConstant.ChartAxis_MinHeight * 7 + ChartConstant.ChartContentWidth * 2;
break;
case 4:
width = y4_length + ChartConstant.ChartAxis_MinHeight * 9 + ChartConstant.ChartContentWidth * 2;
break;
}
return width;
}
高度是确定的 就是最小高度*13然后上边加个轴名高度 下边加个最低高度。宽度就是获取5条线的宽度 比一比谁更长就选谁。
下边是onDraw()方法,讲下思路,具体代码去github上看源码吧。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawpolygon(canvas);
drawAxis(canvas);
drawAxisText(canvas);
}
看图确定哪个在最底部。
首先是几何图,然后花轴承,然后画字。Over!
实践篇3 (ChartContentView&Y_ChartGroup)
ChartContentView
一个自定义EditText 主要就是有两个属性 一个isChecked(判断是否选中,选中就要设置这个为多边形的一个点) 一个Point(就是中心点的意思)
然后就是onMeasure()设置宽高为Constant里的宽高
Y_ChartGroup
主要为了管理方便,因为这个轴上的控件都是变化控件。Z轴均为不变控件。
实践篇4(ChartGroup)
这个是自定义ViewGroup
自定义ViewGroup与自定义View的区别就是多了一个onLayout()排版
其他的都差不多。onMeasure()也有一丢丢的变化。
按步骤来
第一步 初始化。
addView:
- PathView
- Z轴上的ChartContentView
- Y_ChartGroup
private void init(Context context){
zlist = new ArrayList<>();
ygrouplist = new ArrayList<>();
screen_width = DisplayUtils.getScreenWidth(context);
screen_height = DisplayUtils.getScreenHeight(context)-DisplayUtils.dip2px(context,65);
left_distance = DisplayUtils.dip2px(context,20);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mScroller = new Scroller(context);
AXIS_START = DisplayUtils.dip2px(context,80);//
z_distance=DisplayUtils.dip2px(context,33);
PathView pathView = new PathView(context);
ChartConstant.Global_PathView = pathView;
addView(pathView);
for (int i=0;i<str_z.length;i++){//Z轴上的组件
ChartContentView view = new ChartContentView(context);
view.setOnLongClickListener(onLongClickListener);
view.unlockMaxLength();
view.setText(str_z[i]);
view.setTag(R.id.chart_content_id,i);
view.setTag(R.id.chart_content_type,ChartConstant.TYPE_Z);
view.setFocusable(false);
addView(view);
ChartUtils.SetZPoint(view);
zlist.add(view);
}
pathView.setZList(zlist);
for (int i=4;i>=0;i--){//y43210
Y_ChartGroup y = new Y_ChartGroup(context);
y.setTag(i);
y.setY_Type(getY_type(i));
addView(y);
ygrouplist.add(y);
}
}
第二步 Measure
看图得知 我们的宽高基本是以PathView的宽高为基准。宽度就是多了最左边的一个控件的宽度,but 我在确定PathView的宽度的时候多给了一个控件的宽度 。so直接setpathView的宽度跟高度就可以。
第三步 Layout
就是确定位置 然后调用子View的 onlayout方法 确定子View的展示的位置,一开始错误的认知了后四个参数,后四个参数是父布局对于他的父布局的边距距离,不应直接用于子布局中,记住这个就可以了。
第四不 onTouch事件
因为分辨率的问题 必须要写成可拖动的 就是要用到Scroller 具体不会用的可自行百度,Google相关用法,不多说,让你们看下实现代码吧。毕竟关于自定义View 事件分发的文章太多了,推荐爱神的自定义系列文章。
private float mXDown,mYDown,mXLastMove,mYLastMove;
private float mXMove,mYMove;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if(mScroller!=null&&!mScroller.isFinished()){
mScroller.abortAnimation();
}
mXDown = ev.getRawX();
mYDown = ev.getRawY();
mXLastMove = mXDown;
mYLastMove = mYDown;
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
mYMove = ev.getRawY();
mXLastMove = mXMove;
mYLastMove = mYMove;
float diffX = Math.abs(mXMove - mXDown);
float diffY = Math.abs(mYMove - mYDown);
// 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
if (diffX > mTouchSlop||diffY>mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mXMove = event.getRawX();
mYMove = event.getRawY();
int scrollY= (int) (mYLastMove-mYMove);
int scrollX = (int) (mXLastMove-mXMove);
Log.e("Test","scrollX:"+getScrollX()+"====scrollY:"+getScrollY());
scrollBy(scrollX,scrollY);
mXLastMove =mXMove;
mYLastMove = mYMove;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
boolean needScroll= false;
int distance_y=0;
int distance_x=0;
if(getScrollY()<0||getMeasuredHeight()<=screen_height){
distance_y = -getScrollY();
needScroll = true;
}else if(getScrollY()+screen_height>getMeasuredHeight()){
distance_y = -(getScrollY()+screen_height - getMeasuredHeight());
}
if(getScrollX()<0||getMeasuredWidth()<=screen_width){
distance_x = -getScrollX();
needScroll = true;
}else if(getScrollX()+screen_width>getMeasuredWidth()){
distance_x = -(getScrollX()+screen_width-getMeasuredWidth());
needScroll = true;
}
if(needScroll){
mScroller.startScroll(getScrollX(),getScrollY(),distance_x,distance_y,500);
invalidate();
}
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
super.computeScroll();
}
End
当然,许多地方还得优化,不过我感觉这个需求没多久就要被砍。且应该大部分公司都用不到这个东东,有具体需要自行更改吧,这只是一个思路。
源码已传至github
github地址:https://github.com/yudehai0204/AxisChartDemo