當(dāng)前位置:首頁 > 芯聞號 > 充電吧
[導(dǎo)讀]在很多App上我們經(jīng)??吹竭@些效果:淘寶首頁自動(dòng)滾動(dòng)的圖片展示效果支付寶應(yīng)用第一次啟動(dòng)啟動(dòng)的用戶引導(dǎo)畫面要實(shí)現(xiàn)這些效果,有些控件可以幫助我們:1. ViewPager2. ViewFlipper3.

在很多App上我們經(jīng)??吹竭@些效果:

淘寶首頁自動(dòng)滾動(dòng)的圖片展示效果支付寶應(yīng)用第一次啟動(dòng)啟動(dòng)的用戶引導(dǎo)畫面


要實(shí)現(xiàn)這些效果,有些控件可以幫助我們:

1. ViewPager

2. ViewFlipper

3. Fragment


好吧,我最終選擇了ViewFlipper,因?yàn)樗氖褂米罡蓜徘逅ㄍ锩娣艓讉€(gè)View就行了),而且還支持自動(dòng)播放。


不過,為了實(shí)現(xiàn)上面兩個(gè)效果,還得解決來年兩個(gè)問題:

1. 如何在ViewFlipper滾動(dòng)的時(shí)候,獲得當(dāng)前展示的child id?

2. 和ViewPager原生支持觸摸滑動(dòng)翻頁不同,ViewFlipper需要自己監(jiān)聽手勢來解決。


問題1:其實(shí),我挺奇怪的,Google既然為ViewAnimation做了個(gè)ViewFlipper的子類來支持自動(dòng)翻滾,怎么不給它添加一個(gè)listener以監(jiān)聽翻滾事件呢?

好吧,既然google不給,那就自己加。首先看ViewFlipper的源碼(


/*
?*?Copyright?(C)?2006?The?Android?Open?Source?Project
?*
?*?Licensed?under?the?Apache?License,?Version?2.0?(the?"License");
?*?you?may?not?use?this?file?except?in?compliance?with?the?License.
?*?You?may?obtain?a?copy?of?the?License?at
?*
?*??????http://www.apache.org/licenses/LICENSE-2.0
?*
?*?Unless?required?by?applicable?law?or?agreed?to?in?writing,?software
?*?distributed?under?the?License?is?distributed?on?an?"AS?IS"?BASIS,
?*?WITHOUT?WARRANTIES?OR?CONDITIONS?OF?ANY?KIND,?either?express?or?implied.
?*?See?the?License?for?the?specific?language?governing?permissions?and
?*?limitations?under?the?License.
?*/

package?android.widget;

import?android.content.BroadcastReceiver;
import?android.content.Context;
import?android.content.Intent;
import?android.content.IntentFilter;
import?android.content.res.TypedArray;
import?android.os.Handler;
import?android.os.Message;
import?android.util.AttributeSet;
import?android.util.Log;
import?android.view.accessibility.AccessibilityEvent;
import?android.view.accessibility.AccessibilityNodeInfo;
import?android.widget.RemoteViews.RemoteView;

/**
?*?Simple?{@link?ViewAnimator}?that?will?animate?between?two?or?more?views
?*?that?have?been?added?to?it.??Only?one?child?is?shown?at?a?time.??If
?*?requested,?can?automatically?flip?between?each?child?at?a?regular?interval.
?*
?*?@attr?ref?android.R.styleable#ViewFlipper_flipInterval
?*?@attr?ref?android.R.styleable#ViewFlipper_autoStart
?*/
@RemoteView
public?class?ViewFlipper?extends?ViewAnimator?{
????private?static?final?String?TAG?=?"ViewFlipper";
????private?static?final?boolean?LOGD?=?false;

????private?static?final?int?DEFAULT_INTERVAL?=?3000;

????private?int?mFlipInterval?=?DEFAULT_INTERVAL;
????private?boolean?mAutoStart?=?false;

????private?boolean?mRunning?=?false;
????private?boolean?mStarted?=?false;
????private?boolean?mVisible?=?false;
????private?boolean?mUserPresent?=?true;

????public?ViewFlipper(Context?context)?{
????????super(context);
????}

????public?ViewFlipper(Context?context,?AttributeSet?attrs)?{
????????super(context,?attrs);

????????TypedArray?a?=?context.obtainStyledAttributes(attrs,
????????????????com.android.internal.R.styleable.ViewFlipper);
????????mFlipInterval?=?a.getInt(
????????????????com.android.internal.R.styleable.ViewFlipper_flipInterval,?DEFAULT_INTERVAL);
????????mAutoStart?=?a.getBoolean(
????????????????com.android.internal.R.styleable.ViewFlipper_autoStart,?false);
????????a.recycle();
????}

????private?final?BroadcastReceiver?mReceiver?=?new?BroadcastReceiver()?{
????????@Override
????????public?void?onReceive(Context?context,?Intent?intent)?{
????????????final?String?action?=?intent.getAction();
????????????if?(Intent.ACTION_SCREEN_OFF.equals(action))?{
????????????????mUserPresent?=?false;
????????????????updateRunning();
????????????}?else?if?(Intent.ACTION_USER_PRESENT.equals(action))?{
????????????????mUserPresent?=?true;
????????????????updateRunning(false);
????????????}
????????}
????};

????@Override
????protected?void?onAttachedToWindow()?{
????????super.onAttachedToWindow();

????????//?Listen?for?broadcasts?related?to?user-presence
????????final?IntentFilter?filter?=?new?IntentFilter();
????????filter.addAction(Intent.ACTION_SCREEN_OFF);
????????filter.addAction(Intent.ACTION_USER_PRESENT);
????????getContext().registerReceiver(mReceiver,?filter);

????????if?(mAutoStart)?{
????????????//?Automatically?start?when?requested
????????????startFlipping();
????????}
????}

????@Override
????protected?void?onDetachedFromWindow()?{
????????super.onDetachedFromWindow();
????????mVisible?=?false;

????????getContext().unregisterReceiver(mReceiver);
????????updateRunning();
????}

????@Override
????protected?void?onWindowVisibilityChanged(int?visibility)?{
????????super.onWindowVisibilityChanged(visibility);
????????mVisible?=?visibility?==?VISIBLE;
????????updateRunning(false);
????}

????/**
?????*?How?long?to?wait?before?flipping?to?the?next?view
?????*
?????*?@param?milliseconds
?????*????????????time?in?milliseconds
?????*/
????@android.view.RemotableViewMethod
????public?void?setFlipInterval(int?milliseconds)?{
????????mFlipInterval?=?milliseconds;
????}

????/**
?????*?Start?a?timer?to?cycle?through?child?views
?????*/
????public?void?startFlipping()?{
????????mStarted?=?true;
????????updateRunning();
????}

????/**
?????*?No?more?flips
?????*/
????public?void?stopFlipping()?{
????????mStarted?=?false;
????????updateRunning();
????}

????@Override
????public?void?onInitializeAccessibilityEvent(AccessibilityEvent?event)?{
????????super.onInitializeAccessibilityEvent(event);
????????event.setClassName(ViewFlipper.class.getName());
????}

????@Override
????public?void?onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo?info)?{
????????super.onInitializeAccessibilityNodeInfo(info);
????????info.setClassName(ViewFlipper.class.getName());
????}

????/**
?????*?Internal?method?to?start?or?stop?dispatching?flip?{@link?Message}?based
?????*?on?{@link?#mRunning}?and?{@link?#mVisible}?state.
?????*/
????private?void?updateRunning()?{
????????updateRunning(true);
????}

????/**
?????*?Internal?method?to?start?or?stop?dispatching?flip?{@link?Message}?based
?????*?on?{@link?#mRunning}?and?{@link?#mVisible}?state.
?????*
?????*?@param?flipNow?Determines?whether?or?not?to?execute?the?animation?now,?in
?????*????????????addition?to?queuing?future?flips.?If?omitted,?defaults?to
?????*????????????true.
?????*/
????private?void?updateRunning(boolean?flipNow)?{
????????boolean?running?=?mVisible?&&?mStarted?&&?mUserPresent;
????????if?(running?!=?mRunning)?{
????????????if?(running)?{
????????????????showOnly(mWhichChild,?flipNow);
????????????????Message?msg?=?mHandler.obtainMessage(FLIP_MSG);
????????????????mHandler.sendMessageDelayed(msg,?mFlipInterval);
????????????}?else?{
????????????????mHandler.removeMessages(FLIP_MSG);
????????????}
????????????mRunning?=?running;
????????}
????????if?(LOGD)?{
????????????Log.d(TAG,?"updateRunning()?mVisible="?+?mVisible?+?",?mStarted="?+?mStarted
????????????????????+?",?mUserPresent="?+?mUserPresent?+?",?mRunning="?+?mRunning);
????????}
????}

????/**
?????*?Returns?true?if?the?child?views?are?flipping.
?????*/
????public?boolean?isFlipping()?{
????????return?mStarted;
????}

????/**
?????*?Set?if?this?view?automatically?calls?{@link?#startFlipping()}?when?it
?????*?becomes?attached?to?a?window.
?????*/
????public?void?setAutoStart(boolean?autoStart)?{
????????mAutoStart?=?autoStart;
????}

????/**
?????*?Returns?true?if?this?view?automatically?calls?{@link?#startFlipping()}
?????*?when?it?becomes?attached?to?a?window.
?????*/
????public?boolean?isAutoStart()?{
????????return?mAutoStart;
????}

????private?final?int?FLIP_MSG?=?1;

????private?final?Handler?mHandler?=?new?Handler()?{
????????@Override
????????public?void?handleMessage(Message?msg)?{
????????????if?(msg.what?==?FLIP_MSG)?{
????????????????if?(mRunning)?{
????????????????????showNext();
????????????????????msg?=?obtainMessage(FLIP_MSG);
????????????????????sendMessageDelayed(msg,?mFlipInterval);
????????????????}
????????????}
????????}
????};
}


代碼還是挺簡單易懂的,就是用了一個(gè)Handler來定時(shí)調(diào)用showNext()。


可惜,mHandler成員是private+final的,我們無法改動(dòng)它了。

不過,既然ViewFlipper不是一個(gè)final類,那就好辦,自定義類繼承ViewFlipper,重載showNext()函數(shù):


package?com.example.viewflipper;

import?android.widget.ViewFlipper;

/**
?*?Created?by?ray?on?7/4/13.
?*/
public?class?MyViewFilpper?extends?ViewFlipper?{

????private?OnDisplayChagnedListener?mListener;

????public?MyViewFilpper(android.content.Context?context)?{
????????super(context);
????}

????public?MyViewFilpper(android.content.Context?context,?android.util.AttributeSet?attrs)?{
????????super(context,?attrs);
????}

????public?void?setOnDisplayChagnedListener(OnDisplayChagnedListener?listener)?{
????????if?(mListener?!=?listener)?{
????????????this.mListener?=?listener;
????????}
????}

????@Override
????public?void?showNext()?{
????????super.showNext();

????????if(mListener?!=?null){
????????????mListener.OnDisplayChildChanging(this,?super.getDisplayedChild());
????????}
????}

????@Override
????public?void?showPrevious()?{
????????super.showPrevious();

????????if(mListener?!=?null){
????????????mListener.OnDisplayChildChanging(this,?super.getDisplayedChild());
????????}
????}

????public?interface?OnDisplayChagnedListener?{
????????void?OnDisplayChildChanging(ViewFlipper?view,?int?index);
????}
}



問題2,就比較好解決了,OnTouchListener + GestureDetector 監(jiān)聽flipper事件,這里就不貼代碼了。


好吧,這樣基本問題都被解決了,為了讓效果好一點(diǎn),又給ViewFlipper加上了進(jìn)入動(dòng)畫和離開動(dòng)畫。


好吧,下方的1,2,3有點(diǎn)難看,大家湊合下。


當(dāng)目前為止,本人對效果還算滿意,但是,還存在兩個(gè)問題:

1. 沒有處理GestureDetectorListener的onScroll函數(shù),也就是說,在用戶的手指離開屏幕前,不會(huì)有滾動(dòng)效果。

2. 因?yàn)槭峭℅estureDetector是通過TouchListener工作的,作為一個(gè)容器,如果內(nèi)部元素消耗掉了touch事件,則touchlistener無法工作,手勢的監(jiān)聽自然也無從談起。不過,僅僅是應(yīng)付圖片瀏覽的話,還是有辦法解決的:

a. 對于用戶引導(dǎo)這樣的場景,圖片展示是不會(huì)消耗touch消息的。

b. 而淘寶首頁的圖片動(dòng)態(tài)則不同,在用戶點(diǎn)擊圖片的時(shí)候,把頁面轉(zhuǎn)到商品的展示頁面,這種情況下,一般會(huì)考慮使用ImageButton控件來處理Click事件,這樣就導(dǎo)致手勢無法監(jiān)聽。解決方案是,仍舊使用ImageVIew控件,但是在GestureListener的OnSingleTapUp事件上,獲取當(dāng)前展示的頁面,然后根據(jù)頁面信息轉(zhuǎn)到商品展示頁面。

但是,如果是一些比較復(fù)雜的情況,就比較難處理,雖然通過重載onInterceptTouchEvent函數(shù),父容器也能監(jiān)聽所有由子控件消耗的消息,但是也可能存在一些邏輯問題:例如在ViewFlipper容器內(nèi)包含了一個(gè)Gallary控件,那scroll消息應(yīng)該由哪個(gè)控件來處理,亦或都處理?這就是個(gè)比較費(fèi)腦筋的問題了,設(shè)計(jì)UI的時(shí)候,可以考慮設(shè)計(jì)的簡單些。


本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉