日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

实践自定义UI-ViewGroup

發布時間:2025/6/15 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 实践自定义UI-ViewGroup 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前面我們介紹了利用View和Android已有的控件RLF...(RelativeLayout、LinearLayout、FrameLayout...)實踐自定義UI,感興趣的小伙伴請移步:

實踐自定UI—View

實踐自定義UI—RLF...(RelativeLayout LinearLayout FrameLayout....)

接下來我們將利用ViewGroup實踐自定義UI,首先還是看看效果圖:


效果圖

?這個效果是來源于Keep_Growing群里面的一個小伙伴,好像是在項目中需要,問有沒有開源的,后來我發現好像還真的沒有(如果你知道,請告訴我,當然目前實現的功能還沒有達到像ViewPager那么牛,這里主要是想讓大家對利用ViewGroup自定義UI有個很好的認識),所有就想著自己利用ViewGroup實現這個效果。這里利用ViewGroup自定義UI控件,我們主要是注意一下下面兩點:

1.定義規則、屬性:定義一下布局規則,類似于LinearLayout中的orientation、RelativeLayout中的alignParentLeft等。這些規則主要是告訴我們這些子View如何放置他們的位置,以及如何設置大小等屬性。

2.處理交互事件:主要是觸摸事件的處理。

分解效果圖

?我們從上面的效果圖可以很清晰的發現,ViewGroup的子child在滑動的時候,是可以放大和縮小的。那么我們的主要任務之一就是解決這個放大和縮小的效果。我們看一下進入界面的效果如下圖:


靜態圖

從這個靜態的頁面可以看到,就是兩個View,其中第二個View我們可以認為只是按照一定的比例縮小了。根據上面的分析,我們可以這么想象,在ViewGroup中我們添加的一定數量的子View,并且第一個View保持原始大小,剩下的View按一定比例縮小。他們的布局如下圖所示:


示意圖

在滑動的過程中,假如從右向左滑動,那么當前的View會逐漸縮小,下一個View會逐漸放大;假如從左向右滑動,當前的View會逐漸縮小,上一個View會逐漸放大(可以參考效果圖理解)。

實現分解效果圖

?根據上面的分解我們來一步一步實現。

1.測量大小和布局
?為了布局和設置大小的需要,這里我們定義兩個屬性:marginLeftRight和gutterSize,其中marginLeftRight是確定子View與left和right的間距,gutterSize是確定原始大小View與縮小View之間的距離。知道這兩個屬性后我們首先要確定每個View的大小,我們知道這個過程是在onMeasure()方法中完成的(其實onMeasure()方法就是確定當前ViewGroup和子View大小的地方,我們自定義View和ViewGroup都是一樣的),這里還是直接看代碼吧:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//設置默認大小,讓當前的ViewGroup大小為充滿屏幕setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),getDefaultSize(0, heightMeasureSpec));int measuredWidth = getMeasuredWidth();int measuredHeight = getMeasuredHeight();int childCount = getChildCount();//每個子child的寬度為屏幕的寬度減去與兩邊的間距int width = measuredWidth - (int) (mMarginLeftRight * 2);int height = measuredHeight;int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);for (int i = 0; i < childCount; i++) {getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec);}//切換一個page需要移動的距離為一個page的寬度mSwitchSize = width;//確定縮放比例confirmScaleRatio(width, mGutterSize); }

這里首先設置的當前ViewGroup的大小,然后確定每個子View的大小。子View的高度是和ViewGroup的高度相同的,子View的寬度是需要減去剛才設置與兩邊的間距,并調用child.measure()方法確定子View的大小。

?當前ViewGroup的大小和每個子View的大小確定了,接下來的工作就是確定他們在當前ViewGroup中的位置,這個工作當然由onLayout()方法來確定啦,還是直接看代碼吧:

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {int childCount = getChildCount();int originLeft = (int) mMarginLeftRight;for (int i = 0; i < childCount; i++) {View child = getChildAt(i);int left = originLeft + child.getMeasuredWidth() * i;int right = originLeft + child.getMeasuredWidth() * (i + 1);int bottom = child.getMeasuredHeight();child.layout(left, 0, right, bottom);if (i != 0) {child.setScaleX(SCALE_RATIO);child.setScaleY(SCALE_RATIO);}}}

其實這個位置確定的過程可以參考上面的示意圖,首先按照原始的大小將每個子View通過調用child.layout()方法告訴他們在當前ViewGroup中的位置,他們在繪制自己的時候就會在給定的區域內繪制。當這些子View都確定位置時,他們是一個挨著一個的(結合上面的示意圖就可以理解了),并沒有縮小的效果圖,我們調用child.setScaleX()和child.setScaleY()兩個方法設置縮放的大小,那么當child在繪制的時候就會縮小。這里我們怎么知道縮小多少呢,還是看看代碼:

private void confirmScaleRatio(int width, float gutterSize) {SCALE_RATIO = (width - gutterSize * 2) / width; }

這里是根據gutterSize的大小占用整個子View寬度大小的比例,就是縮小的比例,如果不是很理解這個計算方法,可以參考下圖理解一下(這里我們原始大小的和縮小的疊加到了一起):


計算示意圖

2.滑動效果

?上面我們簡單的將測量大小和布局的過程介紹了一下,接下來的工作就是左右滑動的效果實現了,以及處理好滑動過程中的放大和縮小的效果。為了會實現這個效果我們這里簡單的介紹一下需要使用到的類和方法。

(1) Scroller

?滑動的過程我們用到了Scroller這個類,它的主要作用是配合computeScroll(),讓子View滑動到固定的位置。我們先看看Scoller中我們需要使用的方法:

startScroll(int startX, int startY, int dx, int dy, int duration)

這個方法主要的功能是模擬在duration的時間內,在X軸方向上從startX的位置(這里我們只關心X方向,Y方向類似)移動了dx的距離。在這個模擬移動的過程中通過getCurrX() 獲取當前移動到的位置(其實這里大家可以自己查一下這個類的具體用法)。

(2) VelocityTracker

?這個類的主要作用就是檢測手勢滑動的速度。我們滑動View的時候會有一定的速率,當達到一定的速率時我們切換子View。

(3) scrollBy(int x, int y)方法、scrollTo(int x, int y)方法和computeScroll()方法

?scrollBy()方法是在X軸上移動距離為x和Y軸上移動距離為y;scrollTo()方法是移動到(x, y)的位置;computeScroll()方法在我們需要View進行重繪時,就會觸發該方法。當我們需要在規定時間內將View從某個位置滑動到某個固定位置時,可以通過Scroller類模擬這個過程,并通過scrollTo方法配合使用,就可以達到View移動的效果。

?接下來我們將利用上面介紹的方法實現滑動的效果。實現滑動的效果,肯定是對Touch事件的處理,還是直接看代碼:

@Override public boolean onTouchEvent(MotionEvent event) {LogUtils.LogD(TAG, " onInterceptTouchEvent hit touch event");final int actionIndex = MotionEventCompat.getActionIndex(event);mActivePointerId = MotionEventCompat.getPointerId(event, 0);if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);switch (event.getAction() & MotionEvent.ACTION_MASK) {case MotionEvent.ACTION_DOWN:mDownX = event.getRawX();if (mScroller != null && !mScroller.isFinished()) {mScroller.abortAnimation();}break;case MotionEvent.ACTION_MOVE://calculate moving distancefloat distance = -(event.getRawX() - mDownX);mDownX = event.getRawX();LogUtils.LogD(TAG, " current distance == " + distance);performDrag((int)distance);break;case MotionEvent.ACTION_UP:releaseViewForTouchUp();cancel();break;}return true; }private void performDrag(int distance) {if (mOnPagerChangeListener != null){mOnPagerChangeListener.onPageScrollStateChanged(SCROLL_STATE_DRAGGING);}LogUtils.LogD(TAG, " perform drag distance == " + distance);scrollBy(distance, 0);if (distance < 0) {dragScaleShrinkView(mCurrentPosition, LEFT_TO_RIGHT);} else {LogUtils.LogD(TAG, " current direction is right to left and current child position = " + mCurrentPosition);dragScaleShrinkView(mCurrentPosition, RIGHT_TO_LEFT);} }

這里處理的是在手指按住滑動的時候,child的變化,當然最主要的就是放大縮小的變化,由于draScaleShrinkView()方法的代碼比較多,這里就不貼了,我們只要知道該方法就是處理按住左右滑動時child的放大和縮小。我們知道放大過程就是放大比例是從SCALE_RATIO變化到1.0,縮小的過程就是縮小比例從1.0變化到SCALE_RATIO。而且放大的過程是在SCALE_RATIO的基礎上增加的,縮小的過程是在1.0的基礎上減少的。所以移動過程中計算方法如下:

scaleRatio = SCALE_RATIO + (1.0f - SCALE_RATIO) * ratio; shrinkRatio = 1.0f - (1.0f - SCALE_RATIO) * ratio;

我們在切換一個頁面時需要移動的距離為mSwitchSize(這個值我們在前面設置的),那么切換完成后放大或者縮小都變化了(1.0-SCALE_RATIO)。那么在切換的過程中移動的距離與mSwitch的比值我們設為ratio,這個值的變化范圍為:0-1。定義切換一個頁面需要移動的距離為mSwitchSize,當前處于原始大小child的位置為position,當我們向左滑動的時候(向右滑動的過程大家可以試著算一下),計算的過程為:

int moveSize = getScrollX() - position * mSwitchSize; float ratio = (float) moveSize / mSwitchSize;

這個計算的過程估計會有點難理解,大家還是自己想象一下滑動的過程,或者自己比劃一下,這樣便于理解(這里確實比較難理解,我也花了很長時間寫著點內容,希望小伙伴們能自己比劃一下^_^)。這個比例算好后直接調用下面的代碼就可以實現縮放的效果了:

//放大 ViewCompat.setScaleX(scaleView, scaleRatio); ViewCompat.setScaleY(scaleView, scaleRatio); scaleView.invalidate(); //縮小 ViewCompat.setScaleX(shrinkView,shrinkRatio); ViewCompat.setScaleY(shrinkView, shrinkRatio); shrinkView.invalidate();

?以上是滑動過程中的變化,用戶一直處于按住拖動的狀態。當用戶松手之后,那么我們需要根據滑動的速率和當前移動的距離是否超過mSwitchSize(也就是頁面的大小)的一半,判斷是否切換頁面。

private void releaseViewForTouchUp() {final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(velocityTracker, mActivePointerId);float xVel = mVelocityTracker.getXVelocity();//向左滑動,速度大于限定的值滑動到下一個頁面if (xVel > SNAP_VELOCITY && mCurrentPosition > 0) {smoothScrollToItemView(mCurrentPosition - 1, true);//向右滑動時,速度為負數,所以當小于限定值的負數滑動到上一個頁面} else if (xVel < -SNAP_VELOCITY && mCurrentPosition < getChildCount() - 1) {smoothScrollToItemView(mCurrentPosition + 1, true);} else {//沒有達到一定的速度,根據移動的距離確定滑動到哪個頁面smoothScrollToDes();}setScrollState(SCROLL_STATE_SETTLING); }private void smoothScrollToDes() {//整個ViewGroup已經滑動的距離int scrollX = getScrollX();//確定滑動到哪個頁面,mSwitchSize是切換一個頁面ViewGroup需要滑動的距離int position = (scrollX + mSwitchSize / 2) / mSwitchSize;LogUtils.LogD(TAG, " smooth scroll to des position == before =" + mCurrentPosition+ " scroll X = " + scrollX + " switch size == " + mSwitchSize + " position == " + position);smoothScrollToItemView(position, mCurrentPosition == position); }private void smoothScrollToItemView(int position, boolean pageSelected) {mCurrentPosition = position;if (mCurrentPosition > getChildCount() - 1) {mCurrentPosition = getChildCount() - 1;}if (mOnPagerChangeListener != null && pageSelected){mOnPagerChangeListener.onPageSelected(position);}//確定滑動的距離int dx = position * (getMeasuredWidth() - (int) mMarginLeftRight * 2) - getScrollX();mScroller.startScroll(getScrollX(), 0, dx, 0, Math.min(Math.abs(dx) * 2, MAX_SETTLE_DURATION));invalidate(); }

當調用Scroller.startScroll方法后會調用invalidate()方法,這個過程就會觸發computeScroll()方法,我們看看在該方法中我們怎么處理滑動的效果吧,直接看代碼:

@Override public void computeScroll() {if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {dragScaleShrinkView(mCurrentPosition, mCurrentDir);scrollTo(mScroller.getCurrX(), 0);}}

上面我們說了,Scroller.startScroll方法只是模擬移動的過程,通過模擬的過程我們可以在duration的時間內獲取移動到的位置(getCurrX()方法獲取),正真的移動效果還是通過scrollTo()方法實現的,由于我們需要不停的獲取和移動,所以就需要在模擬的時間內不停的調用scrollTo方法,該方法會觸發整個View重繪,會再次調用computeScroll()方法,而我們通過調用Scroller.computeScollOffset()和Scroller.isFinished()方法檢測模擬移動是否結束,從而達到平滑滑動的效果,這個過程中同時要實現放大縮小的效果,上面已經分析了,我就不詳細的介紹了。
?好了,上面我基本上把需要實現了滑屏以及滑動過程中放大縮小的效果了,這個過程其實涉及的東西還是蠻多的,也比較繁瑣,不過不是非常的難。只要仔細的理解每一個過程,還是比較容易理解的,最主要還是多多練習!這里寫的比較多,有可能看的比較暈,如果有興趣的話可以看看源碼吧!

總結

?到此,把自定義UI的三種方法都一一進行了實踐,相信對自定義UI應該有一個感性的認識了。其實更多的時候還是靠自己的練習,只有不斷的實踐才能提高。好了,就寫這么多,如果有不明白的小伙伴,可以隨時交流!

PS

在此感謝程序亦非猿^_^對 實踐自定義UI三篇文章的促成,本來只是想寫一些開源的控件,但是在他的鼓勵下,最終寫了這個系列的博客。

希望在Android學習的路上,大家共同成長!

總結

以上是生活随笔為你收集整理的实践自定义UI-ViewGroup的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美一级免费 | 亚洲成人av在线 | 噼里啪啦国语版在线观看 | 日韩特黄一级片 | 日韩精品在线免费看 | 国产麻豆成人传媒免费观看 | 日本网站免费 | 人妻体体内射精一区二区 | 国产精品99精品久久免费 | 一久久久久 | 夜夜撸小说 | 日韩精品影视 | 欧美成人精品一区二区三区在线看 | 成人动漫在线播放 | 黄色长视频 | 性色免费视频 | 日本91av| 曰本无码人妻丰满熟妇啪啪 | 蜜桃av噜噜 | 里番acg★同人里番本子大全 | 黄色在线免费观看网站 | 国产做爰免费观看 | 欧美极品在线观看 | 骚虎免费视频 | 日日干狠狠干 | 国产在线一区二区 | 男生女生搞黄色 | 天天操操操| 欧美性猛交乱大交3 | 亚洲自拍电影 | 青青草原成人网 | 国产一区中文字幕 | 五月开心激情网 | 亚洲精品大全 | 亚洲欧洲日产av | 九热精品视频 | 国产污网站 | 中国一级片在线观看 | 毛片在线观看网站 | 91国模| 亚洲精品国产suv一区 | 日韩第四页 | 国产婷婷一区二区三区久久 | 日韩性插| 天天舔天天干 | 亚洲精品国产熟女久久久 | 国产精品第六页 | 国产精品国产三级国产三级人妇 | 一级全黄色片 | av电影在线网站 | 丰满少妇一区二区三区 | 久久亚洲精品中文字幕 | 久久婷婷一区二区 | 亚洲最新 | 精品999视频| 久久精品国产精品亚洲毛片 | 日日操日日射 | 成人免费无码大片a毛片抽搐色欲 | 天天操天天插天天射 | 国产一级二级在线 | av在线最新| 色老板精品凹凸在线视频观看 | 国产一级一级 | 国产精品情侣呻吟对白视频 | 一级片视频在线观看 | 日日爱886 | 九色论坛| 高清中文字幕在线a片 | 亚洲中文字幕一区在线 | 国产精品美女在线 | 国产真人做爰毛片视频直播 | 91亚洲精品在线 | 六月婷婷激情 | 欧美日韩精品中文字幕 | 精品毛片在线观看 | 穿情趣内衣被c到高潮视频 欧美性猛交xxxx黑人猛交 | 97超碰中文字幕 | 成人国产视频在线观看 | 亚洲综合影视 | 三上悠亚在线观看一区二区 | 午夜国产福利在线观看 | 一区在线视频 | 上海贵妇尝试黑人洋吊 | 午夜亚洲天堂 | 国产第一区第二区 | 男女日日| 免费欧美一级片 | 波多野一区二区三区 | 亚洲一区二区网站 | 性色福利| 色原网| 红桃视频隐藏入口 | 激情黄色小说视频 | 日韩欧美一区二区三区久久婷婷 | 女人扒开腿让男人捅爽 | 91精品国产91久久久久久吃药 | 亚洲19p| 91禁动漫在线| 一级黄色片网址 |