android自定義組合
1. android自定義View組合控制項
需要看你實現什麼效果。一般自定義控制項可以用2種方式:
1.
將現有的控制項封裝起來,作為一個通用的組件來使用,此時只能用代碼的形式調用,無法再xml中引用,例如將textview和button封裝為一個登錄控制項。
2.
繼承view或者viewgroup,其實viewgroup也是繼承的view。然後依次實現onmeasure()、onlayout()、ondraw(),
1.
onmeasure
--
負責測繪控制項的大小
2.
onlayout
--
負責控制項中子元素擺放的位置
3.
ondraw
--
負責控制項和子控制項的繪制,使其顯示在屏幕中
4.
一些設計和實現較好的自定義view,一般還需要考慮事件的傳遞、動畫的控制、touch事件的處理等
2. Android自定義View基礎篇
在我們自定義View,尤其是製作一些復雜炫酷的效果的時候,實際上是將一些簡單的東西通過數學上精密的計算組合到一起形成的效果。這其中可能會涉及到畫布的相關操作(旋轉),以及一些正餘弦函數的計算等,這些內容就會用到一些角度、弧度相關的知識。
圓一周對應的角度為360度(角度),對應的弧度為2π弧度。
故得等價關系:360(角度) = 2π(弧度) ==> 180(角度) = π(弧度)
幾種創建或使用顏色的方式
Android自定義屬性可分為以下幾步:
2.自定義View中獲取屬性
3.在布局中使用
4.屬性值的類型歸納
3. Android自定義控制項總結
每個view的坐標系原點為左上角那個點,水平方向為x軸,右正左負,豎直方向為y軸,下正上負。
canvas.drawColor //繪制區域塗上顏色(設置底色/蒙層)
canvas.drawCircle(float centerX(圓心X坐標),float centerY(圓心Y坐標),float radius(圓的半徑,單位像素),Paint paint)
canvas.drawBitmap
canvas.drawRect(float left,float top,float right,float bottom,Paint paint) //畫矩形
canvas.drawRect(RecF rect,Paint paint)
canvas.drawRect(Rect rect,Paint paint)
canvas.drawPoint(float x(點X軸坐標),float y(點Y軸坐標),Paint paint)//畫點
點的大小 ->paint.setStrokeWidth(width)
點的形狀 ->paint.setStrokeCap(cap)
ROUND(圓形),BUTT(平頭),SQUARE(方頭)
canvas.drawPoints()//批量畫點
canvas.drawOval(float left(左邊界點),float top(上邊界點),float right(右邊界點),float bottom(下邊界點),Paint paint) //畫橢圓
canvas.drawLine(float startX(起點X軸坐標),float startY(起點Y軸坐標),float stopX(終點X軸坐標),float stopY(終點X軸坐標),Paint paint) (setStyle對直線沒有影響)
canvas.drawLines(批量畫線)
canvas.drawRoundRect(float left,float top,float right,float bottom,float rx(圓角的橫向半徑),float ry(圓角的縱向坐標),Paint paint)//畫圓角矩形
canvas.drawRoundRect(RectF rect,float rx, float ry,Paint paint)
canvas.drawArc(float left, float top, float right, float bottom, float startAngle(起始角度,順時針為正,逆時針為負), float sweepAngle(弧形劃過角度), boolean useCenter(是否連接到圓心), Paint paint) //繪制弧形或扇形 根據弧形所在橢圓進行繪制
canvas.drawPath() //通過描述路徑的方式來繪制圖形
path.addXxx() —添加子圖形
path.addCircle(x,y,radius,dir(路徑方向:順時針/逆時針))
path.xxxTo —畫線
path.lineTo()
path.rLineTo()
path.close() —封閉當前圖形
path.setFillType(Path.FillType ft) //設置填充模式
canvas.drawBitmap(Bitmap bitmap,float left,float top,Paint paint);//畫bitmap
canvas.drawBitmap(Bitmap bitmap,Rect src,RectF dst,Paint paint)
canvas.drawBitmap(Bitmap bitmap,Rect src,Rect dst,Paint paint)
canvas.drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint)
canvas.drawText(String text,float x(起點x坐標),float y(起點y坐標),Paint paint) //繪制文字
Paint.setStyle //設置繪制模式
FILL 填充模式(默認)
STROKE 畫線模式
FILL_AND_STROKE 既畫線又填充
Paint.setStrokeWidth //設置線條寬度 (僅在style:Stroke、FILL_AND_STROLE下有效)
Paint.setTextSize //設置文字大小
Paint.setAntiAlias //設置抗鋸齒開關
Paint.setTextSize(float textSize)//設置文字大小
Paint.setStrokeJoin(Paint.Join join) //設置拐角的形狀
MITER//尖角(默認)
BEVEL//平角
ROUND//圓角
Paint.setStokeMiter(float miter)//設置MITER型拐角的延長線的最大值
設置顏色
直接設置顏色
Paint.setColor(int color)
Paint.setARGB(int a,int r,int g,int b)
Paint.setShader(Shader shader) //設置shader
LinearGradient 線性漸變
RadialGradient 輻射漸變
SweepGradient 掃描漸變
BitmapShader 用bitmap的像素來作為圖形或文字的填充
ComposeShader 混合著色器,多個shader混合使用
Paint.setColorFilter(ColorFilter colorFilter) //設置顏色過濾
Paint.setXfermode(Xfermode xfermode) //以要繪制的內容為源圖像,以View中已有內容作為目標圖像,選取一個PorterDuff.Mode作為繪制內容的顏色處理方案。
色彩優化
Paint.setDither(boolean dither) //設置抖動來優化色彩深度降低時的繪制效果
Paint.setFilterBitmap(boolean filter) //設置雙線性過濾優化Bitmap放大繪制的效果
可以理解為 由馬賽克變成模糊狀態
Paint.setPathEffect(PathEffect effect)//使用PathEffect設置形狀的輪廓效果
CornerPathEffect//把所有的拐角變成圓角
DiscretePathEffect//把線條進行隨機的偏離
DashPathEffect//使用虛線
PathDashPathEffect//使用一個Path來繪制虛線
SumPathEffect//組合效果
ComposePathEffect//組合效果,組合有先後順序
Paint.setShadowLayer(float radius,float dx,float dy,int shadowColor)//添加陰影
Paint.setMaskFilter(MaskFilter maskfilter)//在繪制層上方的附加效果
BlurMaskFilter //模糊效果
new BlurMaskFilter(float radius(模糊范圍),BlurMaskFilter.Blur style(模糊類型))
EmbossMaskFilter//浮雕效果
new EmbossMaskFilter(float[] direction(光源的方向),float ambient(環境光強度),float specular(炫光系數),float blurRadius(光線范圍))
獲取繪制的Path
getFillPath(Path src,Path dst)//實際path
getTextPath(Stirng text,int start,int end,float x,float y,Path)/getTextPath(char[] text,int index,int count,float x,float y,Path path)//文字的path
drawTextOnPath()//沿一條Path來繪制文字
StaticLayout //繪制文字,支持換行
paint.setFakeBoldText(booleab fakeBoldText)//是否使用偽粗體
paint.setStrikeThruText()//是否加刪除線
paint.setUnderLineText(boolean underlineText)//是否加下劃線
paint.setTextSkewX(float skewX)//設置文字橫向錯切角度
paint.setTextScaleX(float scaleX)//設置文字橫向放縮
paint.setLetterSpacing(float letterSpacing)//設置字元間距,默認為0
paint.setTextAlign(Paint.Align align)//LEFT、CENTER、RIGHT默認為LEFT
paint.setTextLocale(Locale locale)/paint.setTextLocales(LocaleList locales) //設置繪制所用的地域
paint.setHinting(int mode)//是否啟用字體微調
測量文字尺寸類:
paint.getFontSpacing();//獲取推薦的行距
paint.getFontMetrics();//獲取point的FontMetrics
baseline:基準線
ascent/descent:普通字元的頂部和底部范圍
top/bottom:限制字型的頂部和底部
leading:行的額外間距,即上一行字的bottm與下一行字的top距離
paint.getTextBounds(String text(測量的文字),int start(文字的起始位置),int end(文字的結束位置),Rect bounds(文字顯示範圍的對象))//獲取文字的顯示範圍
paint.measureText(String text)//測量文字佔用的寬度
measureText()>getTextBounds()
paint.getTextWidths(String text,float[] widths)//獲取字元串中每個字元的寬度,並把結果填入參數widths
paint.breakText(String text((要測量的文字),boolean measureForwards(測量的方向),float maxWidth(寬度上限(超出上限會截斷文字)),float[] measuredWidth(用於接受數據))//測量完成後會把文字寬度賦給measureWidth[0]
paint.getRunAdvance(CharSequence text,int start(文字的起始坐標),int end(文字的結束坐標),int contextStart(上下文的起始坐標),int ContextEnd(上下文的結束坐標),boolean isRtl(文字的方向),int offset(字數的偏移))//計算某個字元處游標的x坐標
paint.getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)//計算出文字中最接近這個位置的字元偏移量
paint.hasGlyph(String s)//檢查指定的字元串是否是一個單獨的字型
canvas.clipRect()//范圍裁剪
canvas.clipPath()//根據范圍裁剪
canvas.translate(float dx,float dy)//位移
canvas.rotate(float degrees,float px,float py)//旋轉
canvas.scale(float sx(橫向縮放倍數),float sy(縱向縮放倍數),float px,float py)//縮放
canvas.skew(float sx(x軸的錯切系數),float sy(y軸的錯切系數))//錯切
canvas.setMatrix(matrix)//用Matrix直接替換Canvas當前的變換矩陣
canvas.concat(matrix)//用Canvas當前的變換矩陣和Matrix相乘
Camera.rotate*()//三維旋轉
1、super.draw()//總調度方法
2、super.onDraw()
3、dispatchDraw()//繪制子View的方法
繪制順序:
draw()總調度方法,view的繪制過程都發生在draw()方法里
1、背景(drawBackground()不能重寫)-------android:background:/View.setBackgroundXxx()
2、主體(onDraw())
3、子View(dispatchDraw())
4、滑動邊緣漸變和滑動條(onDrawForeground())-------android:scrollbarXxx/View.setXXXScrollBarXXX()
5、前景(onDrawForeground())-------android:foreground/View.setForeground()
view.animate().translationX()//x軸偏移
1、如果是自定義控制項,需要添加setter、getter方法
2、ObjectAnimator.ofXXX()創建ObjectAnimator對象
3、用start()方法執行動畫
setDuration(int ration)//設置動畫時長
setInterpolator(Interpolator interpolator)//設置插值器
ViewPropertyAnimator.setListener()/ObjectAnimator.addListener()
ViewPropertyAnimator.setUpdateListener()/ObjectAnimator.addUpdateListener()
ObjectAnimator.addPauseListener()
ViewPropertyAnimator.withStartAction/EndAction()
ArgbEvaluator//顏色漸變動畫
PropertyValuesHolder//同一個動畫中改變多個屬性
PropertyValuesHolders.ofKeyframe()//把同一個屬性拆分
AnimatorSet//多個動畫配合執行
targetSdkVersion>=14,硬體加速默認開啟
view.setLayerType()
LAYER_TYPE_SOFTWARE:使用軟體來繪制View Layer,繪制到Bitmap,並順便關閉硬體加速
LAYER_TYPE_HARDWARE:使用GPU來繪制View Layer,繪制到OpenGL texture(如果硬體加速關閉,那麼行為和LAYER_TYPE_SOFTWARE一致)
LAYER_TYPE_NONE:關閉View Layer
View Layer可以加速無invalidate()(例如動畫)時的刷新效率,但對於需要調用invalidate()的刷新無法加速
硬體加速並不支持所有的繪制操作
1、測量(measure)
View:View在onMeasuer中會計算自己的尺寸然後保存
ViewGroup:ViewGroup在onMeasure中會調用所有子View的measure讓它們進行自我測量,並根據子View
計算出的期望尺寸來計算他們的事跡尺寸和位置然後保存。
2、布局(layout)
View:無子View所以onLayout不做任何處理
ViewGroup:ViewGroup在onLayout中會調用自己所有子View的layout方法,把他們的尺寸、位置傳給他們, 讓他們完成自我布局。
MeasureSpec = mode + size :父類傳遞過來給當前View的一個建議值
MeasureSpec.getMode(int spec)//獲取模式
MeasureSpec.getSize(int spec)//獲取數值
限制分類:
UNSPECIFIED(不限制)
AT_MOST(限制上限)->wrap_content
EXACTLY(限制固定值)->match_parent/具體值
1、重寫onMeasure來修改已有的View尺寸
(1)、重寫onMeasure方法,調用super.onMeasure觸發原有的自我測量。
(2)、在super.onMeasure下用getMeasureWidth與getMeasureHeigh獲取之前測量的結果,使用自己的演算法計算新結果。
(3)、調用setMeasureDimension保存新結果。
2、重寫onMeasure來全新定製自定義View的尺寸
與1區別,保證計算的同時,保證結果滿足父View給出的尺寸限制
(1)重寫onMeasure,計算出View的尺寸
(2)使用resolve讓子View的計算結果符合父View的限制,也可不使用該方法自己定義
3、重寫onMeasure和onLayout來全新定製自定義ViewGroup的內部布局
兩個注意點:
子控制項間的margin值
1、重寫generateLayoutParams()和generateDefaultLayoutParams()
2、獲取margin值 MarginLayoutParams lp = (MarginLayoutParams )child.getLayoutParams()
子控制項間的padding值
1、測量後直接getPaddingLeft、getPaddingTop、getPaddingRight、getPaddingBottom
重寫onMeasure來計算內部布局
(1)調用每個子View的measure來計運算元View的尺寸
結合layout_xxx和自己可用空間
(2)計運算元View的位置並保存子View的尺寸和位置
(3)計算自己的尺寸並用setMeasureDimension保存
重寫onLayout來擺放子View
(1)調用每個子View的layout,讓他們保存自己的位置和尺寸
view工作原理
觸摸事件
1、ACTION_DOWN:手指剛接觸屏幕,按下去的那一瞬間
2、ACTION_MOVE:手指在屏幕上移動
3、ACTION_UP:手指從屏幕上松開的瞬間
事件序列:從ACTION_DOWN -> ACTION_UP
ViewGroup:
DispatchTouchEvent
• return true:表示該View內部消化掉了所有事件
• return false:表示事件在本層不再繼續進行分發,並交由上層控制項的onTouchEvent方法進行消費
• return super.dispatchTouchEvent(ev):默認事件將分發給本層的事件攔截onInterceptTouchEvent方法 進行處理
OnInterceptTouchEvent
• return true:表示將事件進行攔截,並將攔截到的事件交由本層控制項的onTouchEvent進行處理
• return false:表示不對事件進行攔截,事件得以成功分發到子View
• return super.onInterceptTouchEvent(ev):默認表示不攔截該事件,並將事件傳遞給下一層View的 dispatchTouchEvent
OnTouchEvent 默認false
• return true:表示onTouchEvent處理完事件後消費了此次事件
• return fasle:表示不響應事件,那麼該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的 onTouchEvent方法返回true
• return super.dispatchTouchEvent(ev):表示不響應事件,結果與return false一樣
子View不存在分發:
• DispatchTouchEvent 事件分發
• OnTouchEvent 默認true
如下圖為事件分發流程圖:
---------------------- 以上總結部分源自Hencoder教程 ------------------------------
4. android中怎樣實現自定義控制項中的組合控制項
public
class
MyView
extends
View{
//
此處省略
構造方法
private
void
onDraw(Canvas
canvas){
//重寫view的onDraw方法,繪制控制項的樣式
//這里你使用canvas來繪制,你布局中使用這個控制項就是你繪制的樣子
}
//然後你可以定義很多自己的一些方法,用來修改控制項的樣式
//假如你自定義的一個
進度條
的話,就要修改進度條值,你就可以自定義方法,讓實現對象來改變進度值,記得修改後調用validate方法更新顯示。(具體函數記不太清了)
}
大概就是這樣實現的自定義控制項,自定義控制項的話優化是很重要的哦,不然性能會很差。
然後你要使用這個控制項的話,在布局中就需要這樣定義,假如這個自定義控制項類是這樣的:
xxx.xxx.MyView。
則使用時:
5. Android | 自定義上拉抽屜+組合動畫效果
話不多說先來個效果圖看一下
實現的主要功能就是上拉抽屜(解決了子view的滑動沖突)+ 邊緣動畫 + 中間小球和seekbar效果動畫。黃色部分就是上拉抽屜整體,綠色部分是橫向的recyclerview。有個朋友說有阻尼效果就完美了 ... 因為效果圖沒有阻尼效果,所以就沒有去研究 - -!
頂部也可以放個圖片,像醬紫
圓形中間也可以放圖片和文字,上下滑動的時候內部圖片和文字也會隨之改變,其實原理都是一樣的,一個會了你放啥都行,文章後面也會介紹。
效果就是醬紫
抽屜里我放的是LinearLayout,然後動態添加了多個可以橫向滾動的RecyclerView,上滑下滑左滑右滑輕松無壓力~~就是這么刺激
效果介紹完了,下面我們看一下如何實現的
在當前demo里
到這里就可以實現如效果圖一樣的滾動效果了
這樣看就比較直觀些
這個就是用貝塞爾曲線畫的簡單的一個效果
我這里是用了兩個三階貝塞爾曲線,從中間分開,左邊一個右邊一個,然後吧這個視圖上下分為一半,中間的點不變,兩邊的高度增加,兩邊是扇形畫的圓角,然後lineto畫成封閉圖形,這樣就出現了如上圖所示的動畫效果。
這部分大家應該就比較熟悉,自定義view經常會用到,用法就不多說了,記錄一下中間圖片隨之縮放和透明改變的寫法
然後再動畫中不斷改變圓和圓環的半徑、圖的尺寸、畫筆透明度,就能達到效果
抽屜的弧度、圓、圓環和圖片這些的改變主要是監聽當前上滑的距離和需要上滑的距離做的百分比計算的然後相應的隨之改變。
是在父view的滾動監聽里做的改變,topHeight就是抽屜需要滾動的距離。
之前接觸的動畫都是單獨的模塊,直接開始結束的那種,像這次這樣需要動態改變而且多個結合的還是第一次遇到(渣渣本渣沒錯了),所以也是在邊學邊寫,可能有很多地方寫的不是很恰當,也是希望大佬可以指出,共同學習共同進步。其實現在的效果是大改過一次的,最初貝塞爾曲線高度取的整個高度,然後改變中間的那個點向下凹,但是外面的圓又要正好一半在他的上方一半在下方,這樣的位置其實是不好做適配的,所以就改成了現在的這樣。通過這個動畫的實現,自己不僅是在自定義view、動畫還是一些思考方式上都有所進步,這是挺重要的。項目中還有另一個動畫,就下篇再講吧~
gitee項目地址
https://gitee.com/yoyo666/TopScrollView.git
6. Android中的自定義控制項
組合控制項:即由原生控制項拼裝而成,不需要自己實現或者繪制具體的頁面內容和效果,常用於標題欄TitlleView
eg:
繼承控制項的意思就是,我們並不需要自己重頭去實現一個控制項,只需要去繼承一個現有的控制項,然後在這個控制項上增加一些新的功能,就可以形成一個自定義的控制項了。這種自定義控制項的特點就是不僅能夠按照我們的需求加入相應的功能,還可以保留原生控制項的所有功能。
熟悉view的繪制原理
1.measure用來測量View的寬和高。
2.layout用來確定View在父容器中放置的位置。
3.draw用來將view繪制在屏幕上