Android仿微信列表滑動(dòng)刪除之可滑動(dòng)控件(一)
這次是列表滑動(dòng)刪除的第三波,仿微信的列表滑動(dòng)刪除。先上個(gè)效果圖:

前面的文章里面說(shuō)過(guò)開(kāi)源框架SwipeListView的實(shí)現(xiàn)原理是每個(gè)列表item中包含上下兩層view,普通狀態(tài)下上層的view覆蓋著下層的view,當(dāng)用戶(hù)滑開(kāi)上層的view,下層的view就顯示出來(lái)了。但是仔細(xì)觀察微信列表的item,很明顯并非這個(gè)實(shí)現(xiàn)方案,微信的item應(yīng)該一個(gè)單層view,只不過(guò)這個(gè)item超出了所在的ListView的寬度,在用戶(hù)滑動(dòng)item的時(shí)候,item超出屏幕的view則會(huì)顯示在屏幕之上,這種滑動(dòng)實(shí)現(xiàn)也很不錯(cuò)。
既然推測(cè)出微信的實(shí)現(xiàn)原理,現(xiàn)在就要尋找具體的實(shí)現(xiàn)方案了,我最開(kāi)始想的比較簡(jiǎn)單,以為寫(xiě)一個(gè)橫向線性布局LinearLayout,讓其包含兩個(gè)子布局,左邊的子布局寬度設(shè)置為填充父布局的寬度,以為另一個(gè)子布局就自然而然的會(huì)超出父布局的顯示范圍,但是具體測(cè)試的時(shí)候,發(fā)現(xiàn)就算左邊的子布局設(shè)置為填充父布局的寬度,但實(shí)際顯示的時(shí)候還是兩個(gè)子布局被包含在父布局的顯示范圍內(nèi),右邊的子布局無(wú)法做到超出父布局的顯示范圍。
糾結(jié)了一段時(shí)間,偶然情況下想起了ScrollView還有一種類(lèi)型是HorizontalScrollView,,這個(gè)android提供的HorizontalScrollView可以做到其子view超出它的顯示范圍,那我可以直接使用HorizontalScrollView來(lái)實(shí)現(xiàn)這個(gè)item,我決定自定義一個(gè)控件,繼承自HorizontalScrollView,在代碼里面直接添加兩個(gè)子布局,并讓左邊的布局寬度填充這個(gè)控件的自身寬度,以便讓右邊的布局超出控件的顯示范圍,不過(guò)很不辛,HorizontalScrollView比較傲嬌,很難駕馭,奇葩bug層出不窮,譬如左邊的子布局渲染出來(lái)了,但是右邊的子布局愣是沒(méi)初始化,更別說(shuō)滑動(dòng)了,弄的焦頭爛額,最后實(shí)在沒(méi)辦法,只得暫時(shí)放下。
最后則是想到了使用一個(gè)自定義的ViewGroup來(lái)實(shí)現(xiàn)這個(gè)item。現(xiàn)在很多app在第一次啟動(dòng)的時(shí)候,會(huì)出現(xiàn)一個(gè)介紹性的導(dǎo)航界面,用戶(hù)一頁(yè)一頁(yè)的滑動(dòng),看完導(dǎo)航的介紹之后再正式進(jìn)入app,現(xiàn)在這種導(dǎo)航介紹應(yīng)該大多數(shù)是用ViewPager實(shí)現(xiàn)的,ViewPager可以做到兩個(gè)滑動(dòng)的子頁(yè)同時(shí)顯示在屏幕范圍內(nèi),具有很好的體驗(yàn)效果。但是ViewPager是在之后的support.v4里面引入的,最初并沒(méi)有,那一開(kāi)始這種導(dǎo)航介紹使用什么方案實(shí)現(xiàn)的呢?當(dāng)然就是自定義的ViewGroup了,其實(shí)supprot.v4.ViewPager本身就是一個(gè)自定義的ViewGroup。關(guān)于使用自定義的ViewGroup實(shí)現(xiàn)導(dǎo)航介紹,csdn上有個(gè)大牛有專(zhuān)門(mén)寫(xiě)過(guò)一個(gè)文章介紹了,這里就不詳細(xì)說(shuō)了。
本篇的要講的是如何使用自定義的ViewGroup實(shí)現(xiàn)item的子view可以超出父布局的顯示范圍這樣的效果。
寫(xiě)一個(gè)SwipeItemView,繼承自ViewGroup,構(gòu)造方法里面,傳入左邊布局的引用id和右邊布局的引用id,初始化左子布局和右子布局,并將它們添加到SwipeItemView中作為子view,代碼如下:
private void init(Context context, AttributeSet attrs) {
mScroller = new Scroller(context);
......
if(mPrimaryViewID == -1)
throw new RuntimeException(
"Illegal attribute 'primaryView', make sure you have set it");
mPrimaryView = LayoutInflater.from(getContext()).inflate(
mPrimaryViewID, null);
mPrimaryView.setClickable(false);
addView(mPrimaryView, 0);
if(mSlidingViewID != -1) {
mSlidingView = LayoutInflater.from(getContext()).inflate(
mSlidingViewID, null);
mSlidingView.setClickable(false);
addView(mSlidingView, 1);
}
}
接下來(lái)需要重寫(xiě)VIewGroup的onMeasure()方法,用來(lái)測(cè)量這個(gè)SwipeItemView及其子view的寬高,其中先獲取轉(zhuǎn)入的heightMeasureSpec中包含的heightSize,當(dāng)heightSize的值和heightMeasureSpec相同的時(shí)候,是測(cè)量整個(gè)SwipeItemView的寬高,這是我們不做額外的處理,當(dāng)heightSize不等于的傳入的heightMeasureSpec的時(shí)候,是用于測(cè)量SwipeItemView它包含的子view的寬高,這里我們做一下額外的處理,主要是針對(duì)超出SwipeItemView顯示范圍的右邊的mSlidingView,我想要它的寬只是包裹其內(nèi)容就行,不想它的寬和屏幕范圍等寬,所以構(gòu)造一個(gè)這樣的參數(shù)MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),具體代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if(heightSize != heightMeasureSpec) {
mPrimaryView.measure(MeasureSpec.makeMeasureSpec(widthSize, widthMode),
MeasureSpec.makeMeasureSpec(heightSize, heightMode));
if(mSlidingView != null) {
mSlidingView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(heightSize, heightMode));
}
} else {
mPrimaryView.measure(widthMeasureSpec, heightMeasureSpec);
if(mSlidingView != null)
mSlidingView.measure(widthMeasureSpec, heightMeasureSpec);
}
}
然后是重寫(xiě)ViewGroup的onLayout()方法,用來(lái)放置子view在SwipeItemView中的具體位置,主要就是讓右邊的mSlidingView排列在左邊的mPrimaryView的右邊,具體代碼如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mPrimaryView.layout(l, t, r, b);
if(mSlidingView != null)
mSlidingView.layout(r, t, r + mSlidingView.getMeasuredWidth(), b);
}
好了,具體的SwipeItemView的初始化完成了,接下來(lái)需要做什么工作呢,看一下上面的構(gòu)造方法,有沒(méi)有看到mScroller = new Scroller(context)這行代碼,我們需要使用這個(gè)mScroller來(lái)做這個(gè)SwipeItemView滑動(dòng)的動(dòng)畫(huà)效果。我們知道ViewGroup中提供了scrollBy()和scrollTo()兩個(gè)方法用來(lái)移動(dòng)這個(gè)ViewGroup視圖內(nèi)容的位置,其中scrollBy()移動(dòng)參數(shù)指定的距離,scrollTo()方法移動(dòng)到參數(shù)指定的位置。但是scrollBy()還好,如果每次都是移動(dòng)一小段距離的話,給用戶(hù)的感覺(jué)就是一段連續(xù)的動(dòng)畫(huà)效果了,但是scrollTo()則是瞬間移動(dòng),中間沒(méi)有任何動(dòng)畫(huà)效果,會(huì)讓人感覺(jué)到非常突兀,這樣我們就需要用到mScroller這個(gè)對(duì)象了。
Scroller是android提供的用來(lái)實(shí)現(xiàn)我們需要的移動(dòng)動(dòng)畫(huà)效果的。Scroller本身并非去執(zhí)行移動(dòng)的動(dòng)畫(huà)的,感覺(jué)上Scroller更多是作為一個(gè)指揮者,當(dāng)我們調(diào)用它的Scroller.startScroll()方法的時(shí)候,這個(gè)方法我們需要傳入移動(dòng)初始的位置、移動(dòng)的距離以及花費(fèi)的時(shí)間,簡(jiǎn)單理解的話,我們假設(shè)指定的時(shí)間是10s,那第3s的時(shí)候,ViewGroup視圖內(nèi)容應(yīng)該移動(dòng)到什么位置,第7s,應(yīng)該移動(dòng)到哪個(gè)位置,而且這個(gè)時(shí)刻Scroller計(jì)算出來(lái)的位置可以通過(guò)Scroller.getCurrX()和Scroller.getCurrY()獲取到,這個(gè)過(guò)程,Scroller會(huì)回調(diào)ViewGroup中的computeScroll()方法,在這個(gè)回調(diào)方法中,我們調(diào)用scrollTo()執(zhí)行具體的移動(dòng)操作。而我們實(shí)現(xiàn)的這個(gè)scrollToWithAnimation()方法就是提供給后面的SwipeListView用來(lái)移動(dòng)某個(gè)item并且使其移動(dòng)過(guò)程帶有動(dòng)畫(huà)效果的方法。代碼如下:
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
}
}
/**
* just like scrollTo(), but with animation :D
* */
public void scrollToWithAnimation(int scrollX, int scrollY) {
mScroller.abortAnimation();
mScroller.startScroll(getScrollX(), getScrollY(),
scrollX - getScrollX(), getScrollY() - scrollY, 300);
}
接下來(lái)需要實(shí)現(xiàn)一個(gè)自定義的ListView,暫且命名為SwipeListView。下一篇繼續(xù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android自定義控件實(shí)現(xiàn)可左右滑動(dòng)的導(dǎo)航條
- Android控件之SlidingDrawer(滑動(dòng)式抽屜)詳解與實(shí)例分享
- Android開(kāi)源堆疊滑動(dòng)控件仿探探效果
- Android自定義控件ScrollView實(shí)現(xiàn)上下滑動(dòng)功能
- Android實(shí)現(xiàn)可滑動(dòng)的自定義日歷控件
- Android控件SeekBar仿淘寶滑動(dòng)驗(yàn)證效果
- Android自定義View實(shí)現(xiàn)隨手勢(shì)滑動(dòng)控件
- Android自定義滑動(dòng)解鎖控件使用詳解
- Android自定義控件實(shí)現(xiàn)滑動(dòng)開(kāi)關(guān)效果
- Android自定義雙向滑動(dòng)控件
相關(guān)文章
Android?廣播接收器BroadcastReceiver詳解
Android開(kāi)發(fā)的四大組件分別是:活動(dòng)(activity),用于表現(xiàn)功能;服務(wù)(service),后臺(tái)運(yùn)行服務(wù),不提供界面呈現(xiàn);廣播接受者(Broadcast Receive),勇于接收廣播;內(nèi)容提供者(Content Provider),支持多個(gè)應(yīng)用中存儲(chǔ)和讀取數(shù)據(jù),相當(dāng)于數(shù)據(jù)庫(kù),本篇著重介紹廣播組件2022-07-07
Android中ViewPager的PagerTabStrip與PagerTitleStrip用法實(shí)例
這篇文章主要介紹了Android中ViewPager的PagerTabStrip與PagerTitleStrip用法實(shí)例,這兩個(gè)子控件一般被用作添加標(biāo)題,在實(shí)際效果上并不是那么好控制,使用的時(shí)候需要謹(jǐn)慎,需要的朋友可以參考下2016-06-06
Android?webview攔截H5的接口請(qǐng)求并返回處理好的數(shù)據(jù)代碼示例
這篇文章主要給大家介紹了關(guān)于Android?webview攔截H5的接口請(qǐng)求并返回處理好的數(shù)據(jù)的相關(guān)資料,通過(guò)WebView的shouldInterceptRequest方法,Android可以攔截并處理WebView中的H5網(wǎng)絡(luò)請(qǐng)求,需要的朋友可以參考下2024-10-10
Android的App啟動(dòng)時(shí)白屏的問(wèn)題解決辦法
這篇文章主要介紹了Android的App啟動(dòng)時(shí)白屏的問(wèn)題相關(guān)資料,在App啟動(dòng)的第一次的時(shí)候白屏?xí)欢螘r(shí)間,這里提供了解決辦法,需要的朋友可以參考下2017-08-08
android中使用SharedPreferences進(jìn)行數(shù)據(jù)存儲(chǔ)的操作方法
本篇文章介紹了,在android中使用SharedPreferences進(jìn)行數(shù)據(jù)存儲(chǔ)的操作方法。需要的朋友參考下2013-04-04
Android LocationManager獲取經(jīng)度與緯度等地理信息
這篇文章主要介紹了Android LocationManager獲取經(jīng)度與緯度等地理信息的相關(guān)資料,希望通過(guò)本站大家能掌握這樣的知識(shí),需要的朋友可以參考下2017-09-09
Android開(kāi)發(fā)實(shí)現(xiàn)切換主題及換膚功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)切換主題及換膚功能,涉及Android界面布局與樣式動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-03-03
Android使用Theme自定義Activity進(jìn)入退出動(dòng)畫(huà)的方法
這篇文章主要介紹了Android使用Theme自定義Activity進(jìn)入退出動(dòng)畫(huà)的方法,涉及Android的Activity屬性設(shè)置與資源操作技巧,需要的朋友可以參考下2016-07-07

