Android自定義View|復(fù)習回顧

原文:_wangyibo

一菜枷、什么是自定義View?

1. 定義

在Android系統(tǒng)中吼野,看到的應(yīng)用界面都是View棠赛,界面也就是由一個個View組成的亥至,AndroidSdk中為開發(fā)者提供了形形色色的View,比如:顯示文字的TextView川慌,顯示圖片的ImageView,顯示列表數(shù)據(jù)的ListView等等陷猫。但是在開發(fā)想實現(xiàn)一個折線統(tǒng)計圖,這時候系統(tǒng)將不會在滿足需求,需要開發(fā)者去通過自定義view來實現(xiàn)币叹。

2. 如何實現(xiàn)

自定義 View 就是通過繼承 View 或者 View 的子類,并在新的類里面實現(xiàn)相應(yīng)的處理邏輯(重寫相應(yīng)的方法)模狭,以達到自己想要的效果颈抚。

3. View視圖框架結(jié)構(gòu)

通過流程圖可以看出View是所有View的父類,實現(xiàn)一個View都是要繼承View嚼鹉,并且可以看出View分為兩大類View和ViewGroup贩汉,帶著這個問題我們?nèi)タ碫iew和ViewGroup有什么不同之處。

2. 為什么使用自定義View

在開發(fā)中锚赤,開發(fā)者常常會因為下面四個主要原因去自定義 View:

  1. 讓界面有特定的顯示風格匹舞、效果;
  2. 讓控件具有特殊的交互方式宴树;
  3. 優(yōu)化布局策菜;
  4. 封裝;

2.1讓界面有特定的顯示風格酒贬、效果

在開發(fā)中又憨,Android SDK提供了很多控件,但有時锭吨,這些控件并不能滿足業(yè)務(wù)需求蠢莺。例如,想要用一個折線圖來展示一組數(shù)據(jù)零如,這時如果用系統(tǒng)提供的 View 就不能實現(xiàn)了躏将,只能通過自定義 View 來實現(xiàn)锄弱。

2.2 讓控件具有特殊的交互方式

Android SDK提供的控件都有屬于它們自己的特定的交互方式,但有時祸憋,控件的默認交互方式并不能滿足業(yè)務(wù)的需求会宪。例如,開發(fā)者想要縮放 ImageView 中的圖片內(nèi)容蚯窥,這時如果用系統(tǒng)提供的 ImageView 就不能實現(xiàn)了掸鹅,只能通過自定義 ImageView 來實現(xiàn)。

2.3 優(yōu)化布局

有時拦赠,有些布局如果用系統(tǒng)提供的控件實現(xiàn)起來相當復(fù)雜巍沙,需要各種嵌套,雖然最終也能實現(xiàn)了想要的效果荷鼠,但性能極差句携,此時就可以通過自定義 View 來減少嵌套層級、優(yōu)化布局允乐。

2.4 封裝

有些控件可能在多個地方使用矮嫉,如大多數(shù) App 里面的底部 Tab,像這樣的經(jīng)常被用到的控件就可以通過自定義 View 將它們封裝起來喳篇,以便在多個地方使用敞临。

3.如何自定義View?

在了解如何自定義View前,我們先要知道自定義View包含哪些內(nèi)容? 自定義View包含三部分

  1. layout(view的布局)
  2. draw(view的繪制)
  3. 觸摸反饋(view的點擊事件)

在布局階段我們要知道View的尺寸和位置,在繪制階段我嗎要知道view的內(nèi)容,觸摸反饋我們要得到view的點擊事件響應(yīng).

其中布局階段包括測量(measure)和布局(layout)兩個過程,另外view的繪布局階段是為view的繪制和觸摸反饋做支持的,當確定了view的位置我們才能去繪制view設(shè)置view的觸摸反饋

在自定義 View 和自定義 ViewGroup 中,布局和繪制流程雖然整體上都是一樣的麸澜,但在細節(jié)方面挺尿,自定義 View 和自定義 ViewGroup 還是不一樣的,所以炊邦,接下來分兩類進行討論:

  1. 自定義 View 布局编矾、繪制流程
  2. 自定義 ViewGroup 布局、繪制流程

3.1自定義 View 布局馁害、繪制流程

3.2自定義View最基本的流程圖

從View繼承一般需要忙活的方法是onDraw這里

3.3構(gòu)造函數(shù) (獲取自定義參數(shù))

構(gòu)造函數(shù)中我們主要會做一些初始化操作窄俏,以及獲取自己的自定義屬性參數(shù)(如果使用自定義屬性的話), 如果有使用自定義屬性的話,我們可以通過AttributeSet對象attrs獲取他們的值

public LineChartView(Context context) {
    super(context);
}

public LineChartView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
}
復(fù)制代碼

不管是繼承ViewGroup還是View都有四個構(gòu)造重載方式可供選擇,其實四個參數(shù)的是API21之后添加的碘菜。

有三個參數(shù)的構(gòu)造函數(shù)中第三個參數(shù)是默認的Style凹蜈,這里的默認的Style是指它在當前Application或Activity所用的Theme中的默認Style,且只有在明確調(diào)用的時候才會生效忍啸。

我們在寫自定義View的時候需要關(guān)心的通常是有一個和兩個參數(shù)的構(gòu)造方法仰坦。

自定義 View 測量階段

在 View 的測量階段會執(zhí)行兩個方法(在測量階段,View 的父 View 會通過調(diào)用 View 的 measure() 方法將父 View 對 View 尺寸要求傳進來计雌。緊接著 View 的 measure() 方法會做一些前置和優(yōu)化工作悄晃,然后調(diào)用 View 的 onMeasure() 方法,并通過 onMeasure() 方法將父 View 對 View 的尺寸要求傳入凿滤。在自定義 View 中妈橄,只有需要修改 View 的尺寸的時候才需要重寫 onMeasure() 方法庶近。在 onMeasure() 方法中根據(jù)業(yè)務(wù)需求進行相應(yīng)的邏輯處理,并在最后通過調(diào)用 setMeasuredDimension() 方法告知父 View 自己的期望尺寸):

  • measure()
  • onMeasure()

measure() : 調(diào)度方法眷蚓,主要做一些前置和優(yōu)化工作鼻种,并最終會調(diào)用 onMeasure() 方法執(zhí)行實際的測量工作;

onMeasure() : 實際執(zhí)行測量任務(wù)的方法溪椎,主要用與測量 View 尺寸和位置普舆。在自定義 View 的 onMeasure() 方法中,View 根據(jù)自己的特性和父 View 對自己的尺寸要求算出自己的期望尺寸校读,并通過 setMeasuredDimension() 方法告知父 View 自己的期望尺寸。

onMeasure() 計算 View 期望尺寸方法如下:

  1. 參考父 View 的對 View 的尺寸要求和實際業(yè)務(wù)需求計算出 View 的期望尺寸:
  • 解析 widthMeasureSpec祖能;
  • 解析 heightMeasureSpec歉秫;
  • 將「根據(jù)實際業(yè)務(wù)需求計算出 View 的尺寸」根據(jù)「父 View 的對 View 的尺寸要求」進行相應(yīng)的>修正得出 View 的期望尺寸(通過調(diào)用 resolveSize() 方法);
  1. 通過 setMeasuredDimension() 保存 View 的期望尺寸(實際上是通過 setMeasuredDimension() 告知父 View 自己的期望尺寸);

onMeasure (測量View大醒)

measure過程要分情況來看雁芙,如果只是一個原始的View,那么通過measure方法就完成其測量過程钞螟,如果是一個ViewGroup兔甘,除了完成自己的測量過程外,還會遍歷去調(diào)用所有子元素的measure方法鳞滨。

這里面涉及到一個類MeasureSpec洞焙,MeasureSpec代表一個32位int值,高2位代表SpecMode拯啦,低30位代表SpecSize澡匪。SpecMode是指測量模式,而SpecSize是指在某種測量模式下的規(guī)格大小褒链。

SpecMode有三類唁情,每一類都表示特殊的含義

  • UNSPECIFIED

父容器不對View有任何限制,要多大給多大甫匹,這種情況一般用于系統(tǒng)內(nèi)部甸鸟。

  • EXACTLY

父容器已經(jīng)檢測出View所需要的精確大小,這個時候View的最終大小就是SpecSize所以定的值兵迅。她對應(yīng)LayoutParams中的match_parent和具體的數(shù)值

  • AT_MOST

父容器指定了一個可用大小即SpecSize,View的大小不能大于這個值抢韭。她對應(yīng)于LayoutParams中的wrap_content.

onSizeChanged (確定View的大小)

這個函數(shù)在視圖大小發(fā)生改變時調(diào)用喷兼。 一般情況下onMeasure中就可以把View的大小確定下來了篮绰,但是因為View的大小不僅由View本身控制,而且受父控件的影響季惯,所以我們在確定View大小的時候最好使用系統(tǒng)提供的onSizeChanged回調(diào)函數(shù)吠各。

自定義 View 布局階段

layout() : 保存 View 的實際尺寸臀突。調(diào)用 setFrame() 方法保存 View 的實際尺寸,調(diào)用 onSizeChanged() 通知開發(fā)者 View 的尺寸更改了贾漏,并最終會調(diào)用 onLayout() 方法讓子 View 布局(如果有子 View 的話候学。因為自定義 View 中沒有子 View,所以自定義 View 的 onLayout() 方法是一個空實現(xiàn))纵散;

onLayout() : 空實現(xiàn)梳码,什么也不做,因為它沒有子 View伍掀。如果是 ViewGroup 的話掰茶,在 onLayout() 方法中需要調(diào)用子 View 的 layout() 方法,將子 View 的實際尺寸傳給它們蜜笤,讓子 View 保存自己的實際尺寸濒蒋。因此,在自定義 View 中把兔,不需重寫此方法沪伙,在自定義 ViewGroup 中,需重寫此方法县好。

自定義 View 繪制階段

在 View 的繪制階段會執(zhí)行一個方法——draw()围橡,draw() 是繪制階段的總調(diào)度方法,在其中會調(diào)用繪制背景的方法 drawBackground()缕贡、繪制主體的方法 onDraw()翁授、繪制子 View 的方法 dispatchDraw() 和 繪制前景的方法 onDrawForeground():

  • draw()

draw() : 繪制階段的總調(diào)度方法,在其中會調(diào)用繪制背景的方法 drawBackground()善绎、繪制主體的方法 onDraw()黔漂、繪制子 View 的方法 dispatchDraw() 和 繪制前景的方法 onDrawForeground();

drawBackground() : 繪制背景的方法禀酱,不能重寫炬守,只能通過 xml 布局文件或者 setBackground() 來設(shè)置或修改背景;

onDraw() : 繪制 View 主體內(nèi)容的方法剂跟,通常情況下减途,在自定義 View 的時候,只用實現(xiàn)該方法即可曹洽;

dispatchDraw() : 繪制子 View 的方法鳍置。同 onLayout() 方法一樣,在自定義 View 中它是空實現(xiàn)送淆,什么也不做税产。但在自定義 ViewGroup 中,它會調(diào)用 ViewGroup.drawChild() 方法,在 ViewGroup.drawChild() 方法中又會調(diào)用每一個子 View 的 View.draw() 讓子 View 進行自我繪制辟拷;

onDrawForeground() : 繪制 View 前景的方法撞羽,也就是說,想要在主體內(nèi)容之上繪制東西的時候就可以在該方法中實現(xiàn)衫冻。

注意:
Android 里面的繪制都是按順序的诀紊,先繪制的內(nèi)容會被后繪制的蓋住。

自定義 ViewGroup 布局隅俘、繪制流程

自定義 ViewGroup 測量階段

同自定義 View 一樣邻奠,在自定義 ViewGroup 的測量階段會執(zhí)行兩個方法:

  • measure()
  • onMeasure()

measure() : 調(diào)度方法,主要做一些前置和優(yōu)化工作为居,并最終會調(diào)用 onMeasure() 方法執(zhí)行實際的測量工作碌宴;

onMeasure() : 實際執(zhí)行測量任務(wù)的方法,與自定義 View 不同颜骤,在自定義 ViewGroup 的 onMeasure() 方法中唧喉,ViewGroup 會遞歸調(diào)用子 View 的 measure() 方法,并通過 measure() 將 ViewGroup 對子 View 的尺寸要求(ViewGroup 會根據(jù)開發(fā)者對子 View 的尺寸要求忍抽、自己的父 View(ViewGroup 的父 View) 對自己的尺寸要求和自己的可用空間計算出自己對子 View 的尺寸要求)傳入,對子 View 進行測量董朝,并把測量結(jié)果臨時保存鸠项,以便在布局階段使用。測量出子 View 的實際尺寸之后子姜,ViewGroup 會根據(jù)子 View 的實際尺寸計算出自己的期望尺寸祟绊,并通過 setMeasuredDimension() 方法告知父 View(ViewGroup 的父 View) 自己的期望尺寸。

具體流程如下:

  1. 運行前哥捕,開發(fā)者在 xml 中寫入對 ViewGroup 和 ViewGroup 子 View 的尺寸要求 layout_xxx牧抽;
  2. ViewGroup 在自己的 onMeasure() 方法中,根據(jù)開發(fā)者在 xml 中寫的對 ViewGroup 子 View 的尺寸要求遥赚、自己的父 View(ViewGroup 的父 View) 對自己的尺寸要求和自己的可用空間計算出自己對子 View 的尺寸要求扬舒,并調(diào)用每個子 View 的 measure() 將 ViewGroup 對子 View 的尺寸要求傳入,測量子 View 尺寸凫佛;
  3. ViewGroup 在子 View 計算出期望尺寸之后(在 ViewGroup 的 onMeasure() 方法中讲坎,ViewGroup 遞歸調(diào)用每個子 View 的 measure() 方法,子 View 在自己的 onMeasure() 方法中會通過調(diào)用 setMeasuredDimension() 方法告知父 View(ViewGroup) 自己的期望尺寸)愧薛,得出子 View 的實際尺寸和位置晨炕,并暫時保存計算結(jié)果,以便布局階段使用毫炉;
  4. ViewGroup 根據(jù)子 View 的尺寸和位置計算自己的期望尺寸瓮栗,并通過 setMeasuredDimension() 方法告知父 View 自己的期望尺寸。如果想要做的更好,可以在「 ViewGroup 根據(jù)子 View 的尺寸和位置計算出自己的期望尺寸」之后费奸,再結(jié)合 ViewGroup 的父 View 對 ViewGroup 的尺寸要求進行修正(通過 resolveSize() 方法)弥激,這樣得出的 ViewGroup 的期望尺寸更符合 ViewGroup 的父 View 對 ViewGroup 的尺寸要求。

自定義 ViewGroup 布局階段

同自定義 View 一樣货邓,在自定義 ViewGroup 的布局階段會執(zhí)行兩個方法:

  • layout()
  • onLayout()

layout() : 保存 ViewGroup 的實際尺寸秆撮。調(diào)用 setFrame() 方法保存 ViewGroup 的實際尺寸,調(diào)用 onSizeChanged() 通知開發(fā)者 ViewGroup 的尺寸更改了换况,并最終會調(diào)用 onLayout() 方法讓子 View 布局职辨;

onLayout() : ViewGroup 會遞歸調(diào)用每個子 View 的 layout() 方法,把測量階段計算出的子 View 的實際尺寸和位置傳給子 View戈二,讓子 View 保存自己的實際尺寸和位置舒裤。

自定義 ViewGroup 繪制階段

同自定義 View 一樣,在自定義 ViewGroup 的繪制階段會執(zhí)行一個方法——draw()觉吭。draw() 是繪制階段的總調(diào)度方法腾供,在其中會調(diào)用繪制背景的方法 drawBackground()、繪制主體的方法 onDraw()鲜滩、繪制子 View 的方法 dispatchDraw() 和 繪制前景的方法 onDrawForeground():

  • draw()

draw() : 繪制階段的總調(diào)度方法伴鳖,在其中會調(diào)用繪制背景的方法 drawBackground()、繪制主體的方法 onDraw()徙硅、繪制子 View 的方法 dispatchDraw() 和 繪制前景的方法 onDrawForeground()榜聂;

在 ViewGroup 中,你也可以重寫繪制主體的方法 onDraw()嗓蘑、繪制子 View 的方法 dispatchDraw() 和 繪制前景的方法 onDrawForeground()须肆。但大多數(shù)情況下,自定義 ViewGroup 是不需要重寫任何繪制方法的桩皿。因為通常情況下豌汇,ViewGroup 的角色是容器,一個透明的容器泄隔,它只是用來盛放子 View 的拒贱。

4.實戰(zhàn)演練

折線統(tǒng)計圖,可以拖動豎線,并且提示當前點的數(shù)值

自定義屬性的聲明與獲取

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="LineChartView">
        <attr name="horizontal_dotted_color" format="reference|color" />
        <attr name="horizontal_color" format="reference|color" />
        <attr name="vertical_color" format="reference|color" />
    </declare-styleable>

</resources>

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
horizontal_color = typedArray.getColor(R.styleable.LineChartView_horizontal_color, Color.BLUE);
horizontal_dotted_color = typedArray.getColor(R.styleable.LineChartView_horizontal_dotted_color, Color.GRAY);
vertical_color = typedArray.getColor(R.styleable.LineChartView_vertical_color, Color.RED);
typedArray.recycle();

復(fù)制代碼

重寫onDraw

對View的繪制,首先要重寫onDraw方法,在繪制過程中,我們需要用到的兩個關(guān)鍵對象:

  1. Paint:畫筆,使用畫筆對象設(shè)置畫筆的設(shè)置例如顏色,線條的粗細等.
  2. Path:設(shè)置繪制點的開始和結(jié)束位置.
  3. Canvas:畫布,通過drawPath將Paint和Path進行綁定后繪制完成.
//Canvas:畫布
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

}
復(fù)制代碼

舉例使用

//1.1用來繪制文本的畫筆
private Paint textPaint() {
    Paint textPaint = new Paint();
    textPaint.setColor(horizontal_color);
    textPaint.setStrokeWidth(3);
    textPaint.setTextSize(30);
    return textPaint;
}

//2.1用來繪制虛線的畫筆
private Paint dottedPaint() {
    Paint dottedPaint = new Paint();
    dottedPaint.setAntiAlias(true);
    dottedPaint.setStyle(Paint.Style.STROKE);
    dottedPaint.setStrokeWidth(2);
    dottedPaint.setColor(horizontal_dotted_color);
    return dottedPaint;
}
//2.2虛線 用來顯示折線統(tǒng)計圖x軸的線
Paint dottedPaint = dottedPaint();
PathEffect pathEffect = new DashPathEffect(new float[]{5, 5, 5, 5}, 2);
dottedPaint.setPathEffect(pathEffect);

//2.3用來繪制虛線的
private void drawLineX(Canvas canvas, Paint textPaint, Paint dottedPaint) {

    for (int i = 0; i < lineNumY; i++) {
        if (lineNumY - 1 == i) {//繪制折線圖x軸最底部的線
            Paint paint = new Paint();
            Path path = new Path();
            paint.setColor(Color.BLUE);
            paint.setStrokeWidth(3);
            paint.setStyle(Paint.Style.STROKE);
            paint.setAntiAlias(true);
            path.moveTo(marginLeftRight, marginTopBottom + (i * meanHeight));
            path.lineTo(getWidth() - marginLeftRight, marginTopBottom + (i * meanHeight));
            canvas.drawPath(path, paint);
            paint.reset();
        } else {//繪制折線圖x軸的虛線
            Path linePath = new Path();
            linePath.moveTo(marginLeftRight, marginTopBottom + (i * meanHeight));
            linePath.lineTo(getWidth() - marginLeftRight, marginTopBottom + (i * meanHeight));
            canvas.drawPath(linePath, dottedPaint);
        }
        //x軸最右邊的數(shù)值
        String s = (((lineNumY - 1) - i) * meanValue) + "";
        float method = textPaint.measureText(s);
        canvas.drawText(s, marginLeftRight - method - 10, marginTopBottom + (i * meanHeight) + 10, textPaint);
    }

    dottedPaint.reset();
}

//用來繪制折線的畫筆
private Paint brokenLinePaint() {
    Paint brokenLinePaint = new Paint();
    brokenLinePaint.setColor(vertical_color);
    brokenLinePaint.setStrokeWidth(3);
    brokenLinePaint.setStyle(Paint.Style.STROKE);
    brokenLinePaint.setAntiAlias(true);
    return brokenLinePaint;
}
//繪制折線
private void drawLineChart(Canvas canvas, Paint brokenLinePaint) {
    coordBeans.clear();
    Path pathChart = new Path();
    //lintNumX  代表折線共有幾個點   折線的點是根據(jù)是把x軸平均分成了多少份求出的
    for (int i = 0; i < lintNumX; i++) {
        if (i == 0) {//折線的起點
            pathChart.moveTo(marginLeftRight, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i]);
        }
        pathChart.lineTo(marginLeftRight + meanWidth * i, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i] / meanVH);//marginTopBottom + ((lineNumY - 1) * meanHeight) - math[i%6])
        canvas.drawCircle(marginLeftRight + meanWidth * i, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i] / meanVH, 3, brokenLinePaint);
        coordBeans.add(new CoordBean(marginLeftRight + meanWidth * i, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i] / meanVH, axisDataY[i]));
    }
    canvas.drawPath(pathChart, brokenLinePaint);

}

復(fù)制代碼

以上是繪制折線圖統(tǒng)計圖繪制線條的流程

重寫onTouchEvent(MotionEvent event)

接下來我們來看一看,當我們觸摸屏幕是如何獲取到折線的觸摸點

  • 首先要重寫 onTouchEvent(MotionEvent event) 然后在關(guān)注
  • 手指按下操作MotionEvent.ACTION_DOWN
  • 手指移動操作MotionEvent.ACTION_MOVE

@Override
public boolean onTouchEvent(MotionEvent event) {

   switch (event.getAction()) {
          //按下
       case MotionEvent.ACTION_DOWN:
           downX = event.getX();
           downY = event.getY();
           //按下后獲取按下的點是否和折線圖的坐標點是否重合,重合的話提示按下的點的數(shù)值,繪制y軸的線條
           for (int i = 0; i < coordBeans.size(); i++) {
               if (Math.abs(downX - coordBeans.get(i).getCoordX()) < paddingPath / 2
                       && Math.abs(downY - coordBeans.get(i).getCoordY()) < paddingPath / 2) {
                   isClick = true;
                   isClickIndex = i;
                   scrollX = coordBeans.get(i).getCoordX();
                   invalidate();
                   showDetails(isClickIndex);
                   break;
               }
           }
           return true;
           //移動
       case MotionEvent.ACTION_MOVE:

           float x = event.getX();
           float y = event.getY();
         //按下滑動后獲取當前的點是否和折線圖的坐標點是否重合,重合的話提示按下的點的數(shù)值,繪制y軸的線條
           Log.i("onTouchEvent", x + "---" + y);
           if (x >= startX && x <= endX && y >= startY && y <= endY) {
               CoordBean coorBean = getCoorBean(x);
               if (coorBean != null) {
                   invalidate();
                   showDetails(isClickIndex);
               }
           }

           return true;

   }

   return super.onTouchEvent(event);
}
復(fù)制代碼

懸浮提示框

private void showDetails(int index) {
    if (mPopWin != null) mPopWin.dismiss();
    TextView tv = new TextView(getContext());
    tv.setTextColor(Color.WHITE);
    tv.setBackgroundColor(Color.RED);
    tv.setPadding(20, 0, 20, 0);
    tv.setGravity(Gravity.CENTER);
    tv.setText(coordBeans.get(index).getClickCoord() + "");
    mPopWin = new PopupWindow(tv, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    mPopWin.setBackgroundDrawable(new ColorDrawable(0));
    mPopWin.setFocusable(false);
    // 根據(jù)坐標點的位置計算彈窗的展示位置
    int xoff = (int) (coordBeans.get(index).getCoordX() - 0.5 * paddingPath);
    int yoff = (int) (coordBeans.get(index).getCoordY() - paddingPath);
    Log.i("showDetails", coordBeans.get(index).getCoordX() + "---" + coordBeans.get(index).getCoordY());
    mPopWin.showAsDropDown(this, xoff, yoff - getHeight());
    mPopWin.update();
}
復(fù)制代碼

以上就是自定義折線統(tǒng)計圖的基本思路和流程

細節(jié)

  • invalidate() 作用、使用場景梅尤、注意事項
  • View(ViewGroup) 事件分發(fā)

總結(jié)

自定義view主要是三部分

  1. 繪制draw
  2. 布局layout
  3. 觸摸時間event

其中l(wèi)ayout決定了view大小個尺寸,是為view下面的繪制和設(shè)置觸摸事件做基礎(chǔ),一個完整的自定義view三者缺一不可

代碼地址

gitee.com/me_wangyibo…

您的點贊收藏就是對我最大的鼓勵柜思! 歡迎關(guān)注我,分享Android干貨巷燥,交流Android技術(shù)赡盘。 對文章有何見解或者有何技術(shù)問題,歡迎在評論區(qū)一起留言討論缰揪!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陨享,一起剝皮案震驚了整個濱河市葱淳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抛姑,老刑警劉巖赞厕,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異定硝,居然都是意外死亡皿桑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門蔬啡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诲侮,“玉大人,你說我怎么就攤上這事箱蟆」敌鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵空猜,是天一觀的道長绽慈。 經(jīng)常有香客問我,道長辈毯,這世上最難降的妖魔是什么坝疼? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谆沃,結(jié)果婚禮上裙士,老公的妹妹穿的比我還像新娘。我一直安慰自己管毙,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布桌硫。 她就那樣靜靜地躺著夭咬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铆隘。 梳的紋絲不亂的頭發(fā)上卓舵,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音膀钠,去河邊找鬼掏湾。 笑死,一個胖子當著我的面吹牛肿嘲,可吹牛的內(nèi)容都是我干的融击。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼雳窟,長吁一口氣:“原來是場噩夢啊……” “哼尊浪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拇涤,失蹤者是張志新(化名)和其女友劉穎捣作,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹅士,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡券躁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掉盅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片也拜。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怔接,靈堂內(nèi)的尸體忽然破棺而出搪泳,到底是詐尸還是另有隱情,我是刑警寧澤扼脐,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布岸军,位于F島的核電站,受9級特大地震影響瓦侮,放射性物質(zhì)發(fā)生泄漏艰赞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一肚吏、第九天 我趴在偏房一處隱蔽的房頂上張望方妖。 院中可真熱鬧,春花似錦罚攀、人聲如沸党觅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杯瞻。三九已至,卻和暖如春炫掐,著一層夾襖步出監(jiān)牢的瞬間魁莉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工募胃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旗唁,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓痹束,卻偏偏與公主長得像检疫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子参袱,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容