自定义View之产业结构图

需求与结果

技术人信仰:没图说个卵子。

UI设计图:

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:

  1. PathView
  2. Z轴上的ChartContentView
  3. 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容