自定義控件進階02_側滑刪除,粘性控件

1 快速索引 細節問題:算法

1.1 把當前被選中的字母索引置爲灰色,不然爲白色canvas

    每一次在快速索引欄上的觸摸事件都觸發invalidate(),重走onDraw()方法數組

  在onDraw()方法裏,作判斷,若是經過觸摸事件計算的索引與繪製字母數組的索引一致時就更改畫筆的顏色,(記得在觸摸事件中若是手指擡起,就把計算的索引置爲-1)微信

1.2 彈出吐司不太好看,彈出一個圓角的矩形框會好看一些(實際上就是一個圓角的TextView,日常隱藏,滑動的時候顯示)app

圓角:定義背景的xml文件,shape根節點,弧度屬性節點設置便可工具

調用者使用監聽器的時候,獲取到當前選中的索引,傳遞到TextView,並讓它顯示出來,延遲兩秒以後讓它消失便可(使用handler.postDalyed(Runnable,time)).佈局

問題1:延遲兩秒以後消失,在沒消失的時候,索引更換了,在上一個兩秒到達,TextView就消失了,須要在每次獲取到索引以後,移除以前的延遲操做和消失post

handler.removeCallbacksAndMessages(null).學習

 

2,視差特效:下拉界面的時候,費勁的拉動纔出現圖片,:QQ空間,微信朋友圈,背景動畫

實現步驟:能夠重寫ListView overScrollBy方法(自定義ListView)

2.1 填充數據,方便觀察

 

2.2 建立自定義ListView ,重寫構造方法

調用者能夠直接獲取到對象後進行addHeaderView(view)

建立頭佈局文件(須要有視差特效的內容)

若是是圖片:imageViewscaleType屬性:centerCrop(等比例進行縮放,會自動適配,根據不一樣需求來設置不一樣的值便可)

設置了centerCrop以後,動態的修改圖片的高就能夠看到圖片的範圍內容

 

2.3 下拉放大的效果

重寫ListViewoverScrollBy(int delateX,int,delateY,int scrollX,int scrollY,int scrollRangX, int scrollRangY,int maxOverScrollX,int maxOverScrollY,boolean isTouchEvent)

 

①參數解釋

delateY: 豎直方向滑動的瞬時變化量,頂部下拉爲負,底部上拉爲正數

scrollY: 兩端的滑動距離,頂部爲負,底部爲+

scrollRangY: 豎直滑動的範圍

maxOverScrollY: 豎直方向的最大滑動位置(絕對值)

isTouchEvent: 手指拉的爲true,滑動慣性爲false

overScrollBy方法是在滑動到ListView兩端纔會被調用

 

②根據需求,限定事件的觸發範圍

delateY<0,而且是手指滑動的isTouchEvent.

在自定義控件類中,建立一個方法讓調用者能夠設置頭佈局的圖片(由於調用者直接設置addHeaderView,自定義控件類中獲取不到HeaderView)

setParllaxImage(圖片控件)//可是在控件類中,拿不到控件的寬高

//在調用者類中,等待View的樹狀結構所有渲染完畢後,再去setParXX,設置到控件裏

Iv_header.getViewTreeObserver().addOnGloballLayoutListener(new OnGloBallLaoutListener(){})

而後再進行setParXXXX(圖片控件)

而後再把這個回調給移除掉removeGlodBallXXX(this)//否則每次渲染都走這裏

在自定義控件的overScrollBy()方法裏事件觸發範圍,動態的修改這個圖片的高

Int newHeight = iv_header.getHeight() + Math.abs(deltaY)(由於deltaY是負數)

//讓新的值生效

iv_header.getLayoutParams().height = newHeight;

iv_header.requestLayout();//改了值以後,要讓它從新生效

 

③獲取到圖片的原始高度,設置範圍

Iv_header.getDrawable().getIntrinsicHeight();//得到圖片的原始高度

若是新的高度大於原始高度,就再也不增長高度了

 

2.4鬆開回彈動畫

①重寫onTouchEvent()方法,不要刪了super.onTouchEvent()它作了不少判斷

當手指鬆開的時候,獲取到圖片當前的高

int currentHeight = iv_header.getHeight();

ValueAnimator(值動畫,它是ObjectAnimator父類) animator = ValueAnimator.ofInt(開始,結束)

 animator.addUpdateListener(new AnimatorUpdateListener(){

重寫的方法裏,經過參數animation

Float fraction = animation.getAnimatedFraction();//獲取動畫執行的分度值(進度)

Int animatedValue= (int)animation.getAnimatedValue();//獲取中間的值

})

animator.setDuration(500)//設置播放時間

animator.start()//開始播放

這樣就能夠在重寫的方法裏,動態的設置頭圖片的值

中間執行的數值能夠經過typeEvaluator類型佈局器計算,這個值動畫底層已經作了

 

②但願鬆開的時候回彈一下

animator.setInterpolator(new OvershootInterpolator(2))//設置動畫的插補器

OverXxXpolator()//超出射擊的插補器,參數值越大,彈的範圍越大,只彈一次

 

③拖拽圖片的delateY/2,或除以3均可以,讓拖拽顯得比較費勁

 

3,側滑刪除(應用場景,QQ聊天信息,郵件管理等,須要對條目功能進行拓展)

參考ui

 

這裏的尾巴是跟着一塊兒出來的,更高端

3.1 實現步驟:同側滑面板

3.2 每個子條目都是一個容器,繼承FrameLayout的自定義控件

①重寫三個構造.

②建立ViewDragHelper(容器(this),敏感度,回調callback) mHelper

③轉交觸摸事件攔截判斷,處理觸摸事件

onInterceptTouchEvent()中進行攔截判斷mHelper.shouldXXXX();

onTouchEvent()中mHelper.processTouchEvent(event)//tryCatch能夠處理掉多點觸摸的BUG

④重寫回調的方法

 

3.3 界面的初始化

在這個自定義控件容器下有兩個子容器LinearLayout(前佈局,後佈局)

設置好它們的佈局便可

 

3.3.1初步實現:

ViewDragHelper只能對孩子生效,不能對孫子生效

在回調的方法裏:

tryCaptureView()裏 返回true,對全部孩子生效

clampViewPositionHorizontal()返回left建議移動的位置便可,返回值決定將要移動到的位置

 

3.4 拖拽事件的傳遞

3.4.1 在onFinishInflate()中找到兩個子孩子

 在onSizeChanged()//獲取孩子的到寬(高不須要)

mBackView.getMeasuredWidth()//獲取到寬

3.4.2 回調方法裏

clampViewPositionHorizontal()限制範圍 ,

前佈局移動範圍在-backViewWidth>>0之間

後局部移動範圍在wWidth - backViewWidth>>wWidth之間

 

3,4.3 擺放佈局位置,後佈局應該在屏幕的右邊以外

在onLayout()佈局方法裏,建立方法layoutContent(false)//擺放內容,默認關閉

①設置前佈局位置:

Rect rect = computeFrontRect(isOpen)//抽取一個方法,計算出前佈局的矩形位置

//若是是關閉狀態

int left = 0;

若是打開狀態就是mRange(就是-backViewWidth)

返回一個矩形 new Rect(left,0,left + mWidth + ,0 + mHeight);

而後再mFrontView(前佈局).layoutXXXX(矩形參數)

這樣寫的話,各類複雜的業務邏輯都能計算,方便拓展

 

②後佈局位置,在前佈局的尾巴後,抽取方法名,參考computeBackRectViaFront(rect)

計算後佈局的區域範圍

返回一個矩形 new Rect(rect.left,0,left+mRange,0 + mHeight);

 

③更改佈局順序,調整顯示效果(後佈局覆蓋前佈局)

bringChildToFront(view)//把任意佈局順序調整到最上面

 

④但願在拖拽的時候,動態的修改兩個佈局位置

在回調方法裏onViewPositionChanged()當位置變化時,把水平變化量傳遞給另外一個佈局

把發生的dx(偏移量)傳遞給另外一個佈局便可

能夠寫(佈局).layout();

不過這裏用(另外一個佈局).offsetLeftAndRight(偏移量);

//兼容低版本

Invalidate()//刷新數據

 

3.5 鬆手的時候平滑動畫

回調方法 onViewReleased()//鬆開的時候被調用

xvel//水平方向加速度,yvel//豎直方向的加速度

判斷:

若是水平方向的加速度等於0,而且前佈局的位置小於-mRange*0.5f(後佈局的一半)就open();

若是水平方向加速度大於0就close();小於0 open();

這時候就能夠調用前面的layoutContent(boolean)方法

不過直接調用沒有動畫,因此還要執行一個平滑動畫

判斷是不是平滑,不然直接執行layoutContent(boolean)方法跳過去

若是是,開啓一個平滑動畫

判斷觸發平滑動畫mHelper.smoothSlidView(控件,最終left,botton)

//重繪界面,invalidate()方法會有丟幀的狀況

 返回爲true>>>ViewCompat.postInvalidateOnAnimation(this)

 

維持動畫繼續,重寫computScroll()方法,執行下一幀

在這個方法裏

mHelper.continueSetting(this)//返回的是true,表明動畫還未執行完

ViewCompat.postInvalidateOnAnimation(this)//維持平滑動畫的繼續

 

3.6 監聽回調

狀態分析:三種,打開,關閉,滑動

使用一個枚舉表示狀態Close,Open,Swiping

默認爲Close

監聽回調.....

調用者不須要關心中間滑動的過程

因此對外的監聽回調,只須要Close,Open,方法便可

因此還須要定義一個監聽器方法

onStartOpen()方法和onStartClose()方法//

 

狀態更新在回調的onViewPositionChanged()方法裏調用作分發狀態

更新當前的狀態,參考方法名:dispatchDragEvent()

Status = updateStatus()//用一個方法去判斷

在updateStatus()裏,判斷前界面.left的位置

//獲取完最新狀態,與老狀態作比較

若是不一致,就更新老狀態,並調用接口的方法.

當新狀態是滑動狀態,而且與老狀態不一致,根據老狀態的不一樣,

若是是開啓,就調用onstartOpen()方法,不然onStartClose();//表明正在要打開或關閉

 

3.7 添加到ListView裏

當控件被當作條目添加到ListView裏,根節點的寬高會被改爲適應ListView,解決方法

能夠在子節點裏設置寬高,或者直接在根節點裏指定mineHeight最小的高度.

爲在ListVIew進行判斷當前滑動的條目,因此在建立回調方法時,把this(當前滑動對象)傳遞進去.

調用者在適配器類中,記錄下每個被打開的條目,若是關閉了就移除掉.

調用者能夠在onStartOpen()中,關閉掉這個集合中全部的對象,遍歷關閉

 

4.粘性控件(主要學習自定義View):

應用場景:未讀提醒 參考效果:

 

 

4.1 ①繪製一幀的界面效果(固定值)

畫一個拖拽圓,和中間的鏈接部分,以及固定圓

建立一個類GooView繼承自View,在onDraw()方法裏進行繪製

//畫一個圓

canvas.drawCircle(x,y,半徑,畫筆)

Paint = new Paint(Paint.ANTI_ALIAS_FLAG);//設置抗鋸齒

paint.setColor();//設置顏色

 

//畫曲線:cavas沒有直接畫曲線的方法,可是能夠畫一條路徑

參考分析圖

 

 

cavas.drawPath(path,paint)

Path path = new Path();

//跳到點1開始畫

path.moveTo(x,y);

//從點1到點2畫曲線 貝塞爾曲線算法

path.quaTo(cx,cy,ex,ey)//二階曲線,執行這個方法若是沒有調用moveTo()就從0.0開始

前兩個座標是控制點的座標(此案例的控制點就是沙漏的中心點)

Path.close();//自動封閉,兩點之間的直線與曲線之間的區域進行填充

//從點2到點3畫直線

Path.linto(x,y)//這兩個點屬於直線

//點3到點4

Path.quadTo( cx,cy,ex,ey);

//點4到點1能夠不畫,由於已經填充完了

 

②讓固定值變成變量

 

 

PointF()//點的封裝類,封裝了一個點的X,Y座標

把兩個圓的固定屬性替換成變量.

 

找到固定圓的兩個附着點(點1,4)座標,這兩個個點封裝到一個PointF()的數組裏,

找到拖拽圓的兩個附着點(點2,3)座標,這兩個個點封裝到一個PointF()的數組裏

而後畫路徑

Path.XXX方法的參數用這些封裝點對象的值進行替換

 

③計算變量

計算四個附着點座標,(圓的座標一個是固定,一個是根據觸摸事件設置)

GeometryUtil幾何學工具類

GeometryUtil.getIntersectionPoints(圓心座標,半徑,角度linek)(返回一條直線的)

lineK=兩個圓心Y軸的差值/X軸的差值//若是X差值爲0,就爲null便可,工具類有對策

 

一個控制點的座標

GeometryUtil.getMiddlePoint(圓1,圓2)//獲取兩個圓之間的中間點

 

畫出附着點(參考使用)(方便觀察,畫四個附着點小圓點)

 

4.2 根據觸摸事件動態繪製

觀察可知,隨着拖拽圓與固定圓的圓心距離越遠,固定圓半徑越小

觸摸事件中

①按下的時候getRawX()//若是getX()是不能超過父控件的區域,因此用getRawX()屏幕座標

更新圓心的座標mDragCenter.set(x,y)//pointF封裝的方法

updateDragCenter(x,y)//更新座標

若是發現座標錯位,把標題去掉

仍是有細微的錯位,由於還有狀態欄的問題

能夠把狀態欄去掉,或者獲取X,Y之類,這裏把畫布向上平移畫布,就能夠把座標修正

Canvas.translate(x,y)//畫布平移

 

②獲取狀態欄高度onSizeChanged()裏

Utils.getStatusBarHeight(View)//傳入誰的View均可以,

//獲取屏幕可視的矩形框(不包含標題欄)

其實是使用了view.getWindowVisibleDisplayFrame(frame(Rect))

返回frame.top就能獲得可用屏幕的頭座標

 

③Canvas.save()//保存當前畫布位置狀態

Canvas.translate(0,-frame.top);

Canvas.restore()//恢復畫布移動以前,畫的小圓點位置仍是在移動以後的.

 

④根據兩個圓心的距離,計算出固定圓的半徑

建立一個方法,在裏面經過工具類GemotryUtil.getXXXXX2Point(圓心1,圓心2);

返回一個float數,表示兩個圓心之間的距離

//能夠定義一個最遠距離,80f比較合理

經過類型估值器,計算從固定圓12f的半徑>>>3f的半徑

比值:圓心的距離/最大距離,若是超過最大距離,就一直等於80

而後把算出來的半徑值傳遞回來,更改方法裏面的值:固定圓半徑,和附着點的座標

 

類型估值器中,矩形估值器做用:在選擇的時候傳入開始矩形和最後一個矩形,能夠獲取到中間的全部矩形框(機頂盒應用中經過遙控器選中項目時,上下左右選中項目的時候,每次跳一個矩形框,這個框的位置就是這樣算出來的)

 

4.3 事件處理.

4.3.1 畫出最大範圍(圓環,參考使用)

paint.setStyle(Style.STROKE);//填充邊線

canvas.drawCircle(固定圓心.x,固定圓,半徑>>最大範圍(80),畫筆paint);

paint.setStyle(Style.FILL);//恢復原樣

 

4,3,2 事件1:當拖拽圓的超出最大範圍,再也不繪製鏈接曲線和固定圓

移動的時候

Float d = 經過工具類獲取兩個圓之間的距離

if(d>最大範圍){

isOutofRange = true;//是否超出最大範圍

刷新數據

}

在繪製的方法裏,作判斷

若是超出最大範圍就再也不繪製鏈接曲線和固定圓

 

按下的時候

重置爲isOutofRang = false;

  isDisappear = false;

事件2:鬆手的時候

根據isOutofRang來判斷,

若是爲true,

分支:在範圍外:

再獲取一次兩個圓的距離,若是仍是大於最大範圍,就更改一個變量

Boolean isDisappear = true;

而後刷新數據.

繪製的方法裏,若是爲true,就不繪製全部控件

 

若是小於最大範圍,重設一下拖拽圓的範圍,修正爲固定圓的圓心位置

 

若是爲false

分支:在範圍內鬆手,播放一個動畫,恢復原來

ValueAnimator animator = ValueAnimator.ofFloat(1.0f);

animator.addUpdateListener(new XXXListener){

重寫的方法中,參數animation

animation.getAnimatedFraction();//獲取動畫的進度,至關於獲取到了一切!

獲取到了進度,就能夠設置拖拽圓的動畫

在外面先獲取到拖拽圓的圓心PointF startP = new PointF(x,y);

GeometryUti.getPointByPercent(startP,mStichCenter(固定圓),進度)//返回個點

讓這個點生效

更新數據

updateDragCenter(x,y)//這是本身寫的方法

}

animator.setDuration(500);

//建立一個插補器,讓拖拽圓彈一下,用戶體驗更好

animator.setInterpolator(new OverShootInterpolator(4));

Animator.start();

 

4.4 事件的監聽回調

建立一個接口提供方法

 onReset(boolean)恢復後調用(超出範圍恢復true,和沒超出範圍恢復)

 onDisapper()//消失後調用

在鬆開事件里根據事件類型調用方法

在範圍內鬆開以後,動畫播放完畢才調用onReset(false)

動畫結束的監聽:animator.addListener(new AdimatorListenerAdater(){

重寫動畫播放完畢的方法,不用強制重寫全部方法,在這裏面調用onReset(false)方法

})

 

4.5 小細節:

 

 

拖拽控件與ListView的結合,小紅點自己是個TextView

點擊這個TextView,生成一個WindowManager,而後在WindowManager上生成一個全屏的透明的控件(TextView能夠),自定義View添加到上面去,就能夠全屏移動了

ViewParent parent = getParent();//獲取父容器

而後把TextVie的觸摸事件傳遞給自定義View gooView

parent.requestDisallowInterceptTouchEvent(true)//請求父類不攔截touch事件

當恢復的時候,經過WindowManager把本身給移除掉

畫文本,就是畫完拖拽圓以後,畫一個一樣座標的Text(再給它單獨搞個畫筆,設置大小,設置顏色,居中對齊方式)

 

5,快捷鍵

 

相關文章
相關標籤/搜索