天天看点

教你如何在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中实现“仿携程首页的按钮”点击缩放效果

继续阅读