天天看點

教你如何在GridView的Item中實作“仿攜程首頁的按鈕”點選縮放效果

    “攜程在手,說走就走”——近年來,攜程APP已經随着鄧超的這句廣告詞大量出現在很多都市人的手機程式安裝清單中。操作過攜程的朋友都知道它的首頁的按鈕風格和點選縮放的動畫吧~如下圖所示:

教你如何在GridView的Item中實作“仿攜程首頁的按鈕”點選縮放效果

    不得不說,這個布局很簡潔大方有木有?!這個點選效果帶來的使用者體驗真的很酸爽有木有!它的點選回報設計思路為:使用者按下去按鈕縮小(或者說是陷進去),手指釋放,按鈕就還原。

    由于這點選體驗效果實在魔性,是以我在使用GridView的時候,也突發奇想,想在使用者點選Item的時候,也出現類似的動畫。于是經過一段時間的設計、編寫、修改、完善……終于實作了這該死的效果。為了能博得一個更良好的使用者體驗也是拼了!下面就是我最終實作的效果:

教你如何在GridView的Item中實作“仿攜程首頁的按鈕”點選縮放效果

    需要說明一下,示範圖裡面有的按鈕點選的快,有的很慢,看起來像是卡頓一樣。請注意:這不是卡頓!我是為了示範按下和釋放有兩種動畫效果而故意做出來的(最後一次按“已送出”那個item的時候,我故意按住很久才釋放),實際操作中是很流暢的,沒有任何問題,是以這一點要請放心。

    好了,廢話不多說,進入今天的正題——

    首先,這個點選按下、松開還原的效果,很明顯是GridView的item子View的一個縮放動畫,也就是Android開發動畫篇裡面的補間動畫之一:ScaleAnimotion . 那麼無疑它就是我們今天要着重讨論的主要技術支援了。是以起步思路就有了:想辦法獲得GridView的item View,然後,分别定義一個縮小動畫和一個放大動畫,将這兩個動畫對象在對應控制條件下應用到子view對象。很顯然,這個控制條件肯定是包含在GridView的onTouch觸發事件裡面的。通過回調函數的使用者觸屏動作參數,來控制我們的子View是該縮小還是該放大。

    OK~上面說得有點兒亂,下面整理一下完整的實作思路:

    ①編寫縮小和放大的動畫代碼或xml檔案;

    ②實作GridView的onTouch事件監聽;

    ③判斷使用者點選的是GridView的哪個item,擷取該item的子View;

    ④擷取使用者觸屏事件,選擇相應的動畫播放對象應用到此子View.

    There!以上就是抽取出來的完整實作思路,下面我們一步一步來搞定它!

    首先,我這裡采用的是代碼的方式來編寫了縮小和放大的動畫,因為這裡可能隻用一次,是以,沒有采用定義xml檔案的方式,其實,屬性都是一樣的。

private void initItemAnim() {
    //縮小動畫
  ItemDownAnim = new ScaleAnimation(1.0F, 0.90F, 1.0F, 0.90F, 1, 0.5F, 1, 0.5F);
    ItemDownAnim.setDuration(200L);
    ItemDownAnim.setFillAfter(true);

    //放大動畫
  ItemUpAnim = new ScaleAnimation(0.90F, 1.0F, 0.90F, 1.0F, 1, 0.5F, 1, 0.5F);
    ItemUpAnim.setDuration(100L);
    ItemUpAnim.setFillAfter(true);
}      

    代碼如上面所示,很簡單。隻是兩個縮放動畫而已,如果你對Android動畫還不夠熟悉的話,可以先大概查閱文檔了解一些必要的屬性解釋。我這裡采用了三句代碼來指定我這個動畫……應該怎麼配合演出的我進行表演(嘎嘎!~): 首先縮小時,要以自身的中心為原點進行縮放,即指定相對自身變化的pivotX及pivotY都取值為50%,其次,還要求它從原尺寸縮小到原來的0.9(當然,這個縮小的程度自己可以控制),接着,這個動畫要播放200ms的時間,否則會很難看出這中間的漸變特效,就會沒有陷進去的那種魔性。最後,我們給它指定播放完畢之後就停留在目前狀态,即給setFillAfter的值為true . 縮小動畫完了之後就是放大動畫,它很像縮小動畫的逆過程,但是我們讓它彈起來速度快點兒,将動畫播放時間設定為100ms,其它屬性相信不用我多說,大家都知道怎麼做。

    Next,進入Step 2 :實作GridView的觸摸事件監聽。那麼我們就自定義一個GridView對象,通過findViewByid方法,拿到布局檔案裡面的GridView,然後通過調用setOnTouch監聽方法并實作接口方法onTouch來控制使用者點選事件。這個接口傳入了使用者觸摸事件類MotionEvent的對象event,這裡我們用switch語句來選擇我們要播放的動畫,switch語句傳入的判斷條件即是event對象所調用的類的動作屬性,這裡主要用到兩個重要的動作屬性:ACTION_DOWN和ACTION_UP,代表按下和釋放。核心代碼如下:

//實作首頁功能點選動畫測試:
indexGrid.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isTouchItem = true ;
                indexGrid.getChildAt(itemPosition).startAnimation(ItemDownAnim);
                break;
            case MotionEvent.ACTION_UP:
               recoverItem();
                break;
            default:
                break;
        }
        return false;
    }
});      

   如上面代碼所示:不同的動作屬性下面執行了不同的動畫。這裡請大家先無視boolean類型的變量isTouchItem的存在,因為它的存在是因為我的設計需求而在後期優化中添加的。這個我後面可能還會寫部落格來講解,暫時先不管。 好了,代碼很清晰,不同的觸摸動作下面都執行了不同的代碼邏輯,在觸摸事件為ACTION_DOWN的時候,其實已經包含了我們實作步驟的第三步,也就是擷取使用者到底點選了哪個Item,這裡我采用了一句代碼來得到GridView的item的View對象,但是需要依靠itemPosition變量,這個變量是什麼意思?又從哪兒來?不急,我先貼出我的GridView的Adpter的getView()方法的實作代碼你可能就明白了:

public View getView(final int position, View convertView, ViewGroup parent) {
    final View gridItem = LayoutInflater.from(getActivity()).inflate(R.layout.grid_item, null);
    ImageView imageView = (ImageView) gridItem.findViewById(R.id.id_ItemPic);
    TextView textView = (TextView) gridItem.findViewById(R.id.id_ItemText);
    imageView.setImageResource(funList.get(position).funImageId);
    textView.setText(funList.get(position).funName);

    DisplayMetrics ds = getResources().getDisplayMetrics();
    int width = ds.widthPixels;
    gridItem.setLayoutParams(new GridView.LayoutParams(width / 4 - 2, width / 4));

    gridItem.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                itemPosition = position;
            }
            return false;
        }
    });

    //GridView載入時動畫顯示:
    ScaleAnimation ItemLodingAnim = new ScaleAnimation(0.0F, 1.0F, 0.0F, 1.0F, 1, 0.5F, 1, 0.5F);
    ItemLodingAnim.setDuration(500);
    ItemLodingAnim.setFillAfter(true);
    gridItem.startAnimation(ItemLodingAnim);

    return gridItem;
}      

    我想大概你是看懂了,原來我在子View的getView裡面也定義了一個onTouch監聽,隻要使用者按下去,就立馬獲得此時getView傳入的position,即是目前使用者點選到的item,當然,這個position要想用它的話,還得依靠一個全局的變量itemPosition來傳輸。隻要itemPosition擷取到點選的item position,即可在GridView的onTouch事件裡面自由使用。

    說到這兒,可能有的朋友忍不住想問了……既然這樣,那你幹嘛不直接在getView()方法的onTouch監聽事件中實作縮放動畫的播放效果呢? 其實我想說……這一點我是嘗試過的,這也是我最初的設計思路,然而後面在測試的過程中卻發現,無論怎樣執行,程式都隻能執行ACTION_DOWN下面的縮小動畫,當你松開手指的時候,卻怎麼樣也彈不起來了。也許是我對getView方法的執行機制還不夠清楚,這一點我将在後面去詳細閱讀它的源碼來找尋答案,但是目前我們先擱置此方法。其他朋友如果找到了問題所在,也可以聯系我,我們再做進一步的讨論。

    OK!繼續——

    上面我們在getView方法裡面得到了使用者點選的item的序号,那接下來問題好辦了…不…其實也不好辦!為什麼這麼說?我後面會講到。

    回到我們的GridView的onTouch監聽中來,剛剛我們既然拿到了item的序号,那麼我們完全可以通過GridView.getChildAt( int index )來拿到我們的item View對象,然後,再startAnimoton(),傳入我們定義好的縮小動畫對象,接着在ACTION_UP事件中再利用此方法傳入放大動畫!OK!完美——

    But!!!真的是這樣嗎?如果你認為是,那你不妨按照你的思路去整一下,你就會發現一個很宏大的畫面:你點選第一次的時候,也許還會乖乖按照我們的設計來顯示縮放動畫效果,那麼!你再嘗試點選第二個item,按下去,松開!呵呵!問題來了,這時候你就會發現剛剛第一次點選的那個item,竟然跟随着第二個item一起執行了放大動畫,不詳的預感随之而來……你再點選第三個、第四個、第五個……就會發現,每次都是這樣,隻要前面點選過的按鈕,都會同時執行放大的動畫!

    這很顯然不是我們想要的效果,我們需要做一些調整。究其原因,原來是Item點選之後的動畫要先被clear掉才能去執行下一個Item的動畫。那麼我們回到ACTION_UP的事件下,此時我們不再單純寫startAnimotion(itemUpAnim),而是在後面加一句:GridView.getChildAt(itemPosition).clearAnimotion() ,再執行看看!果然問題解決了~這次無論點選哪個Item都是單獨播放縮放動畫了,再也不會集體執行。   不過……你再嘗試一下,就會發現新的問題:你若按住Item一段時間再松開,也許還能勉強播放縮小和放大的動畫,但是你要是快速按下再放開,就會發現item根本毫無反應,那是因為你在ACTION_UP下執行了clear(),它都來不及播放縮放動畫就被清掉了。 到這裡……你大概就體會大這個問題不好辦了吧~

    那麼我們到底該怎麼處理這其中的沖突性呢? 其實,也很簡單,隻要我們調整一下思路:點選第一個item的時候,暫時不去clear(),等點第二個的時候,就去清理上一個Item的動畫,但是要注意,第一次和第二次如果是同一個呢?是不是也要做個判斷?Bingo!那麼我們來寫一下代碼:

private void recoverItem() {
    isTouchItem = false ;
    indexGrid.getChildAt(itemPosition).startAnimation(ItemUpAnim);
    if (lastItem >= 0 && lastItem != itemPosition) {
        indexGrid.getChildAt(lastItem).clearAnimation();
    }
    lastItem = itemPosition;
}      

    我在這裡把ACTION_UP事件觸發的邏輯封裝成了一個單獨的方法:recoverItem()~      照舊,我們先無視isTouchItem變量,先看下面的代碼。首先肯定還是執行放大動畫,這沒什麼好說的,重點是後面的處理。我們剛剛說了,要清理掉上一個點選的item的動畫,那麼這上一個item的序号我們是不是要先存下來才行?是以,我們先聲明一個全局變量lastItem,并且給它一個初始值-1~意思是還沒點選任何一個item之前,上一個item不存在。那麼我們來設定if判斷條件:首先上一個item序号不為預設值-1,同時,它也不能和目前點選的item是同一個。兩者都滿足了,才去執行清理上一個Item的動畫。執行完畢再将目前的Item序号重新指派給lastItem,因為此時它就變成了上一個點選的Item了~

    Over!~修改完畢後,重新跑一遍我們的Demo,問題得到了完美解決。接下來你可以一頓痛點!狂點!!狠命點!!!絕對不會有任何問題了。

    到此為止,就是我要分享的有關Item動畫的東西了,接下來我可能要寫一下這個動畫在ViewPager視圖下的一些優化,估計就是我關于GridView這塊兒的最後一篇文章了。

    See you next time!~~~

教你如何在GridView的Item中實作“仿攜程首頁的按鈕”點選縮放效果

【PS:關于上面提到的那個getView方法的問題,哪位大蝦有思路了,可以第一時間聯系我,然後我将作出新的修改~郵箱[email protected]或者留言均可!                     

先在此拜謝——

教你如何在GridView的Item中實作“仿攜程首頁的按鈕”點選縮放效果

繼續閱讀