1. 程式人生 > >自定義View的一些問題

自定義View的一些問題

最近在實現一個拖拽新增View並拖動刪除的元件,分成兩個部分(尚未完成的)和SlideBlock,程式碼在我的github上,需要自取

在這個過程中,我總結了一下自定View的一些問題,分為兩個部分闡述:

第一部分:繪製
SlideBlock繼承自LinearLayout
1、構造器

//繼承自LinearLayout之後需要建構函式,一般兩個就夠用了
public SlideBlock(Context context) {
    this(context,null);
}
public SlideBlock(Context context, @Nullable AttributeSet attrs){
    super
(context, attrs); mContext = context; }

2、元件寬高獲取
我們都知道一個View的三大繪製流程:onMeasure()、onLayout()、onDraw();
其中onMeasure我以前很喜歡用,後來發現它有走多次的問題,而且第一次結果永遠是0,第二次結果可能不對,第三次才是正確的,這一次我改用在三次onMeasure之後執行的onSizeChange()確定寬高,只走一次還準確,但問題也是有的,我目前沒考慮到元件變化的問題。
此時我們已經獲得了元件的寬高,然後可以呼叫函式進行繪製,注意onLayout()雖然也可以反應元件的寬高,但在祂裡面在確定就有些晚了,於是我們在onSizeChange()中呼叫自定義繪製函式setView()

3、繪製
因為元件繼承自LinearLayout,所以本次採用addView的方式新增,而非onDraw中draw的方式繪製,然後我們可以直接設定背景
0)容器屬性
setOrientation(LinearLayout.HORIZONTAL);
setClipChildren(false);
setClipToPadding(false);
1)清空介面removeAllViews();
2)新增元件addView(元件View,new LayoutParams(寬度, 高度))
此處要注意高度全屏可以直接寫可以寫作LayoutParams.MATCH_PARENT,因為此處設定是要在之後輪詢childView時才會生效
3)設定child寬高
迴圈設定child的寬高
View child = getChildAt(i);
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth,MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight,MeasureSpec.EXACTLY);
child.measure(widthMeasureSpec, childHeightMeasureSpec);
需要注意此處childWidth和childHeight不能用LayoutParams.MATCH_PARENT了需要從螢幕寬度計算出具體值
4)onLayout
執行順序是:onMeasure()->onSizeChanged()->、onLayout()->onDraw()
這裡有一個問題:繼承自ViewGroup的自定義元件內容不顯示 ,解決的話有兩個關鍵點:

1)addView之後需要遍歷設定child的寬高,設定後在onlayout中仍可能為零,但不設定的child就顯示不出來
for (int i = 0; i < getChildCount(); i++) {
    final View child = getChildAt(i);
    if(desiredWidth!=0) {
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight,MeasureSpec.EXACTLY);
        child.measure(widthMeasureSpec, childHeightMeasureSpec);
    }
}
2)onLayout中需要遍歷設定child的位置
for (int i = 0; i < count; i++) {
    final View child = getChildAt(i);
    if (child.getVisibility() == View.GONE) {
        continue;
    }
    final int width = child.getMeasuredWidth();//可能為0
    final int height = child.getMeasuredHeight();//可能為0
    //設定child的佔位
    child.layout(childLeft, childTop, childLeft + rowWidth, childTop + height);
    childLeft+=rowWidth;
}

上述例子中是在一個繼承自LinearLayout的元件中橫向添加了兩個LinearLayout,所以才增加left的變數值,元件中這裡做了mode處理
需要注意的是,只要遍歷並設定元件的第一層child的寬高就可以了,不論child是View還是ViewGroup都可以愉快的解決問題

onLayout()的預設值是元件在螢幕中的位置,這個函式本身就是定位用的,把確定好寬高的child逐一取出,設定
child.layout(childLeft, childTop, childRight, childBottom);

4、屬性
經過測試,所有activity/fragment中設定的屬性會先被執行,然後再走onSizeChange()

5、問題點
在寫的時候發現幾處問題特此說明一下
1)子View包含元素過大時如何顯示
如父控制元件設定為橫向,當橫向寬度過大時子View只顯示部分
所以需要通過
ll.addView(createTextView(),new LayoutParams(rowWidth, rowHeight));
設定子View的寬高

2)自定義View時LinearLayout ll = new LinearLayout(mContext);
建立物件後獲取元件ll的getLayoutParams()為null;
所以此處只能通過setLayoutParams(new LayoutParams(rowWidth, rowHeight))

3)佈局是否應當寫在onDraw?
當用元件搭建可以完成時不需要onDraw,但如果涉及到path,convan就需要了

4)在後面的移動中發現一個問題
平移中View的高度變小了,這個現象很奇怪,偵測view.getHeight()沒有變化,也不是margin導致的,最後是同事提醒你的內容文字折行了我才發現是因為文字導致view的背景色範圍出現問題,如圖所示:
這是一張移動的textView高度小於其他textView的圖

第二部分:事件處理

眾所周知View的觸控機制,而我的元件只是判斷滑動距離,因此只對元件的onTouchEvent做處理就滿足需求。
說一下思路:
首先我獲取了event.getX()和event.getY());他們是元件內的X/Y值,與此相對的還有getRawX(),getRawY() 他們是螢幕上的X/Y值
然後根據運算獲取到點選的是哪一行,再逐一獲取View的X/Y從而判斷我們應當處理的那個View。
但此處發生了問題,對View的諸多X/Y函式一頭霧水,在附錄總結一下
關於view的平滑移動我試了幾套方案

方案一:
因為眾所周知的原因,一開始我使用setleft/setright來修改元件位置,效果能達成,可結果出現一個問題,元件的內容不會隨著居中,很生硬也很難看,本想通過給view動態setGravity的方式解決,試了一下搞不定,翻了翻google他說你應該用getTranslationX而不是setLeft。
還是記錄一下程式碼:

TextView leftView = nowList.get(number-1);
//當前操作view左移
moveText.setLeft(moveText.getLeft()+Math.round(moveSize));
//操作view左側的右側變短
leftView.setRight(leftView.getRight()+Math.round(moveSize));

方案二:
使用setTranslationX做修改
這個方案解決了文字居中的痛點,但是移動時會有忽大忽小的白邊,經過多次測試這個空白仍然無法消除,但相對於其他方案而言這個效果還可以接受,目前推測是因為setTranslationX為float和set X這種int轉換造成誤差的緣故

if(olp.width - moveSize>0) {//先做判斷看是修改還是移除
    //先修改右側的偏移
    rightTextView.setTranslationX(moveSize);
    //然後修改容器的寬度
    olp.width = olp.width - moveSize;
    rightTextView.setLayoutParams(olp);
}else{
    removeView(rightTextView);//移除指定view
    nowList.remove(rightTextView);//從快取中釋放
    resetLeftRight();//重置左右兩部分    
}
//再修改當前的偏移
olp = (LayoutParams) moveText.getLayoutParams();
//olp.width = getNowWidth(moveText);
//olp.width = rightTextView.getLeft()+moveSize -moveText.getLeft();//如此這般發現寬度沒有變化
olp.width = olp.width + Math.round(moveSize);
moveText.setLayoutParams(olp); 

上面可以看出對於olp.width的獲取我使用幾種方式:
1)getNowWidth:是一個迴圈取出並累加當前容器內,除指定view外所有view的getWidth()的函式,就結果而言不能解決白邊的問題,他的結果和view.getWidth()是一致的
2)rightTextView.getLeft()+moveSize -moveText.getLeft();
這個方法摒除了float造成的誤差,但實際來說也沒有解決問題
3)olp.width + Math.round(moveSize);目前採用的方式,空白仍然存在

網路方案:不能解決問題
1)scrollBy
rightTextView.scrollBy(-moveSize,0);
moveText.scrollBy(-moveSize,0);
經過測試上面這個方案只有內容平移了,元件沒有動
2)offsetLeftAndRight
rightTextView.offsetLeftAndRight(-moveSize);
moveText.offsetLeftAndRight(-moveSize);
經過測試上面這個方案不能實現元件寬度的變化,它是通過將元件平移實現效果

最後我們整理一下android中view座標相關的知識

1、getX() getY()
這個是view左上角距離父佈局的距離,而且這個距離可能會變化,比如使用動畫將view移動的時候,這兩個座標就會發生變化。

2、getTranslationX() getTranslationY()
view相對於最初位置的變化量。始終是相對於最初的位置。
同時我們也可以使用set方法比如setTranslationX來動態改變view的位置。所以這一組座標存在的意義就是為了view的位置變化使用的。

3、getLeft() getTop() getRight() getBottom()
這四個座標是指一個view的邊際距離父佈局的距離。
Getleft()和getRight()是相對父佈局的左邊,而getTop()和getBootom()是相對於父佈局的上邊。所以我們通過這四個值是可以知道view的寬度和高度的。
需要注意相接的兩個元件A和B,A.getRight==B.getLeft

這三組座標的關係:getX()= getTranslationX()+getLeft()

4、getPivotX() getPivotY()
view旋轉和縮放的時候的中心點,需要注意的是它的值等於寬度的一半,如果要判斷這個點在螢幕的位置還需要(view.getX()+view.getPivotX())

經過組合測試發現(下面返回的值都是px單位)
修改view的距離用setLeft() setTop() setRight() setBottom() 時不會讓內容居中,google也不推薦
修改view的距離用setTranslationX() setTranslationY() 時通過偏移量改變元件X,y值,結合movelp.width的方式改變元件大小時會出現區域性空白的情況,經過測試發現是修改的width沒有及時反映到getWidth()
然後我查了一下區別
getLayoutParams().width返回的是該view向父view請求的最大寬度,不是view實際繪畫的寬度,getMeasuredWidth()與它等效
getWidth()獲取的就是該view的實際寬度
第次改變一個viewgroup中的view寬度需要通過設定getLayoutParams().width和setWidth()的方式,此種方式在快速拉取時也會出現空白
最後結論上方空白是由於view.setText(“text”+text);後寬度變化字顯示不開造成的變化

相關推薦

(七)Flutter 佈局 建立小部件 定義view 關於佈局的一些屬性 mainAxis SizeBox Alignment stack AspectRatio ConstrainedBox

主要內容 建立LayoutDemo小部件 建立可配置圖示徽章IconBadege 小部件 Row與Column mainAxis:主軸與crossAxis 交叉軸 SizeBox 固定尺寸的盒子 Alignment 對齊 stack 一摞小部件

淺談定義View一些常用的回撥方法

1. 構造方法 1.public View(Context context) 2.public View(Context context, @Nullable AttributeSet attrs) 3.public View(Context context, @Nulla

定義View一些問題

最近在實現一個拖拽新增View並拖動刪除的元件,分成兩個部分(尚未完成的)和SlideBlock,程式碼在我的github上,需要自取 在這個過程中,我總結了一下自定View的一些問題,分為兩個部分闡述: 第一部分:繪製 SlideBlock繼承自Line

關於定義view一些問題

最近遇到了較為棘手的重寫view的問題。首先是寫構造方法。按照原生的寫法是單引數構造方法呼叫自己的雙引數構造方法,雙引數構造方法呼叫自己的三個引數的構造方法,而不能使用像編譯器推薦的那樣只調用父類的構造方法。按照原生view的寫法初始化只需在三個引數的構造方法中完成即可,而

【朝花夕拾】Android定義View篇之(六)Android事件分發機制(中)從原始碼分析事件分發邏輯及經常遇到的一些“詭異”現象

前言        轉載請註明,轉自【https://www.cnblogs.com/andy-songwei/p/11039252.html】謝謝!        在上一篇文章【【朝花夕拾】Android自定義View篇之(

Android 定義View

wid declare created odi lex getwidth 實現 tdi led   最近在看鴻洋大神的博客,在看到自定義部分View部分時,想到之前案子中經常會要用到"圖片 + 文字"這類控件,如下圖所示: 之前的做法是在布局文件中,將一個Imag

定義VIew方法

bili change 鍵盤 boolean eve 失去 nat finish bool onFinishInflate() 回調方法,當應用從XML加載該組件並用它構建界面之後調用的方法 onMeasure() 檢測View組件及其子組件的大小 onLayout() 當

定義View總結

class net lin 定義 img view mage .net 技術分享 寫的很好,代你分析原碼,關於 View Measure 測量機制,讓我一次把話說完 自定義View總結

Android零基礎入門第24節:定義View簡單使用

子類 protect jin 討論 我們 @+ amp 進階 運行程序 當我們開發中遇到Android原生的組件無法滿足需求時,這時候就應該自定義View來滿足這些特殊的組件需求。 一、概述 很多初入Android開發的程序員,對於Android自定義View可能比較

簡單定義VIEW報錯問題

nfc 定義 http dnf androi dem and .com android aNDROIDNFC%E8%AF%BB%E5%8D%A1%E5%99%A8%E7%9A%84DEMO http://music.baidu.com/songlist/495819911

Android定義view詳解

this boolean mar 處理 都是 並且 jdk text 命名 從繼承開始 懂點面向對象語言知識的都知道:封裝,繼承和多態,這是面向對象的三個基本特征,所以在自定義View的時候,最簡單的方法就是繼承現有的View 通過上面這段代碼,我定義了一個Ske

Android -- 定義view實現keep歡迎頁倒計時效果

super onfinish -m use new getc awt ttr alt 1,最近打開keep的app的時候,發現它的歡迎頁面的倒計時效果還不錯,所以打算自己來寫寫,然後就有了這篇文章。 2,還是老規矩,先看一下我們今天實現的效果   相較於我們常見的倒計時

Android定義View效果目錄

class 重寫 自定義 textview 居中 url 冒泡 and 雷達圖 1、絢麗的loading動效的實現 2、Android自定義View:進度條+冒泡文本 3、Android雷達圖(蜘蛛網圖) 4、Android文本閃爍 5、Android繪制圓形進度條 6、重

定義View不顯示的問題

idt 不能 pre 寫法 是否為空 錯誤提示 recycler tac fresh 問題描述: 我自定義了一個把 SwipeRefreshLayout 和 RecyclerView 封裝在一起的 View ,但是發現 List 不能正常的顯示出來,本以為是

Android定義View——實現水波紋效果類似剩余流量球

string 三個點 pre ber block span 初始化 move 理解 最近突然手癢就想搞個貝塞爾曲線做個水波紋效果玩玩,終於功夫不負有心人最後實現了想要的效果,一起來看下吧: 效果圖鎮樓 一:先一步一步來分解一下實現的過程 需要繪制一個正弦曲線(sin

定義view

width als tint war 樣式 attr 寬度 rectf declare 1:四個構造方法,其中有一個參數,兩個參數,三個參數,四個參數; 2:四個方法:1:onMeasuer()測量高度,2:onDraw()繪制需要一個畫筆panit3:onLayout()

定義view刮刮樂

save alias eset validate ack prot 移動 clas tag package com.example.guaguale;import android.content.Context;import android.graphics.Bitmap;

定義view圓環的改變

paint contex bool ctf reat ssa log += ret //次線程更新ui Handler handler = new Handler(){ @Override public void handleMessag

Android開發之漫漫長途 番外篇——定義View的各種姿勢2

是個 pub water 常用 getchild mod one 它的 sdn 該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡量按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深入理解Android 卷

定義View分類與流程

ces ted function ram 註意 measure fin 利用 href 自定義View分類與流程(進階篇)## 轉載出處: http://www.gcssloop.com/customview/CustomViewProcess/ 自定義View繪制流程