实现带3D弹性效果的ViewPager(非自定义控件)

来源:互联网 发布:模糊逻辑算法 编辑:程序博客网 时间:2024/05/16 08:20

一、前言

最近有朋友问我,这种效果是怎么做出来的……
这里写图片描述
csdn只能上传小于2M的图片,只好划的快一点了。
效果就是,在第一页和最后一页的时候,继续滑动会有个3D的旋转效果。

第一反应就是jazzViewpager,叫朋友去试试看。结果他说不行,我也没去试,估计jazzViewPager是每个页面都会有动画……
然后我用了一个很笨的方法,就是监听touch事件,用属性动画搞定。

二、实现思路

  1. 判断当前的pager是否是第一个或者最后一个。
  2. 监听滑动事件,根据手指的位移一直改变View的角度。其实重点就在这里了=。=
  3. 当手指松开的时候,用属性动画将View旋转回原来的角度。

三、代码实现

没有自定义一个ViewPager,直接监听onTouch了。以后有时间可能会抽一个自定义出来……

package com.aitsuki.viewpagedemo;import android.animation.ObjectAnimator;import android.app.Activity;import android.os.Bundle;import android.support.v4.view.PagerAdapter;import android.support.v4.view.ViewPager;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import java.util.ArrayList;/** * Created by AItsuki on 2016/1/11. */public class PagerActivity extends Activity {    private ViewPager mViewPager;    private ArrayList<View> mViews;    // 手指按下的位置    private int startX;    // ViewPager当前显示的position    private int mCurrentPage;    // 是否正在进行动画    private boolean onAnimator;    // View的最小滑动距离。    private int mSlop;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_pager);        // 获取View的最小滑动距离。(当滑动超过这个距离,我们才判断这是滑动事件)        mSlop = ViewConfiguration.get(this).getScaledTouchSlop();        // 将需要显示View填充出来, 用集合保存        LayoutInflater inflater = getLayoutInflater();        View page1 = inflater.inflate(R.layout.page1, null);        View page2 = inflater.inflate(R.layout.page2, null);        View page3 = inflater.inflate(R.layout.page3, null);        mViews = new ArrayList<>();        mViews.add(page1);        mViews.add(page2);        mViews.add(page3);        mViewPager = (ViewPager) findViewById(R.id.vp);        MyAdapter adapter = new MyAdapter();        mViewPager.setAdapter(adapter);        // 监听页面切换,获取ViewPager当前显示的position        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {            @Override            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {            }            @Override            public void onPageSelected(int position) {                mCurrentPage = position;            }            @Override            public void onPageScrollStateChanged(int state) {            }        });        // 监听事件,这里就是整个功能的核心了……        mViewPager.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                int action = event.getAction();                switch (action) {                    case MotionEvent.ACTION_DOWN:                        // 不能在这里获取手指按下的事件,因为如果有子View消费了此事件,那么这里是不执行的。//                        startX = (int) event.getRawX();                        break;                    case MotionEvent.ACTION_MOVE:                        // 获得手指按下的位置。                        if(startX == 0) {                            startX = (int) event.getRawX();                        }                        // 获得当前的位置。                        int endX = (int) event.getRawX();                        // 如果是第一个页面                        if(mCurrentPage == 0 ) {                            // 判断手指移动的距离是否大于最小滑动距离                            // 是的话标记当前的状态正在进行动画。                            if(endX - startX > mSlop) {                                onAnimator = true;                            }                            // 如果正在进行动画                            // startX < endX 这个是为了防止向反方向的位置做动画。比如按下手指,往右滑动一丁点,                            // 然后往左滑动。如果不做判断,那么动画的方向就反了。                            if(startX < endX && onAnimator) {                                // 获取当前显示的View                                // 不能直接mViewPager.getCharAt(mCurrentPager),因为ViewPager缓存懒加载机制                                // 的原因,这样获取的View并不一定是当前正在显示的View。                                // 在Adapter的时候就要给View设置一个tag和position关联起来,然后通过                                // mViewPager.findViewWithTag(position)找到该View。                                View view = mViewPager.findViewWithTag(mCurrentPage);                                // 使用属性动画实时改变View的形状, 滑动距离乘以一个系数,不然旋转角度会很大。                                // 你也可以通过设置其他方式计算,比如限制一个最大角度,这里只是图省事。                                // 如果觉得用属性动画太挫,你也可以使用nineoldandroids这个库的ViewHelper,实际上                                // 并没有什么不同。                                ObjectAnimator.ofFloat(view, "rotationY", (endX - startX) * 0.025f).setDuration(0).start();                            }                            // 如果是最后一页。这里的代码和上面类似,就不注释了。                        } else if(mCurrentPage == mViews.size() -1) {                            if(startX - endX > mSlop) {                                onAnimator = true;                            }                            if(startX > endX && onAnimator) {                                View view = mViewPager.findViewWithTag(mCurrentPage);                                ObjectAnimator.ofFloat(view , "rotationY", (endX - startX) * 0.025f).setDuration(0).start();                            }                        }                        break;                    case MotionEvent.ACTION_UP:                    case MotionEvent.ACTION_CANCEL:                        // 当手指抬起或事件取消的时候,我们要将View归位,至于动画的时间,你们也可以根据角度的大小设置时间=。=                        View view = mViewPager.findViewWithTag(mCurrentPage);                        ObjectAnimator.ofFloat(view, "rotationY", 0).setDuration(250).start();                        // 按下的位置归0,不然可能影响下一次手势判断。                        startX = 0;                        // 退出动画状态                        onAnimator= false;                        break;                }                // 如果是动画状态,那么就直接return true,用这里的代码处理事件。                // 否则将事件交给ViewPager处理                // 简单来说: true自己处理,false交给ViewPager处理。没看懂的话建议去看看我的另一篇博客=。=                // "Android的事件分发源码分析,告别事件冲突"                return onAnimator;            }        });    }    class MyAdapter extends PagerAdapter {        @Override        public int getCount() {            return mViews.size();        }        @Override        public boolean isViewFromObject(View view, Object object) {            return view == object;        }        @Override        public void destroyItem(ViewGroup container, int position, Object object) {            container.removeView((View) object);        }        @Override        public Object instantiateItem(ViewGroup container, int position) {            View view = mViews.get(position);            // 这里也是关键,将position设置为view的tag。否则找不到这个View            view.setTag(position);            container.addView(view);            return view;        }    }}

四、如果是FragmentAdapter该怎么使用

将上面那个Demo发给朋友之后,他一直说牛逼,让后有点小小的成就感。结果第二天他跟我说不会用,因为他需要用FragmentAdapter,我一口老血喷涌而出。
核心思想其实就是获取到第一个页面和最后一个页面的根View进行动画。那么Fragment该怎么获取到它的View呢?
我们再初始化Fragment的时候会填充一个View作为Fragment的显示内容,这个View就是我们要操作的对象了,所以我们要将这个View提供出去。



抽一个Fragment的基类,写一个抽象方法让子类实现,得到它们的根View。

/** * Created by AItsuki on 2016/1/11. */public abstract class BaseFragment extends Fragment {    public View mRootView;    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {        mRootView = initView(inflater);        return mRootView;    }    public abstract View initView(LayoutInflater inflater);}


子类继承就可以了Basefragment就可以了。我这里写了三个Fragment,分别是A、B、C

/** * Created by AItsuki on 2016/1/11. */public class AFragment extends BaseFragment {    @Override    public View initView(LayoutInflater inflater) {        return inflater.inflate(R.layout.page1, null);    }}
/** * Created by AItsuki on 2016/1/11. */public class BFragment extends BaseFragment {    @Override    public View initView(LayoutInflater inflater) {        return inflater.inflate(R.layout.page2, null);    }}
/** * Created by AItsuki on 2016/1/11. */public class CFragment extends BaseFragment {    @Override    public View initView(LayoutInflater inflater) {        return inflater.inflate(R.layout.page3, null);    }}



最后就是Activity中的代码了, 就不注释了,和Pager并没有多大区别。
通过adapter.getItem就可以很准确的获取到当前显示的Fragment,不用设置tag那么麻烦。

package com.aitsuki.viewpagedemo;import android.animation.ObjectAnimator;import android.os.Bundle;import android.support.v4.app.FragmentActivity;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.view.ViewPager;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import com.aitsuki.viewpagedemo.fragment.AFragment;import com.aitsuki.viewpagedemo.fragment.BFragment;import com.aitsuki.viewpagedemo.fragment.BaseFragment;import com.aitsuki.viewpagedemo.fragment.CFragment;import java.util.ArrayList;/** * Created by AItsuki on 2016/1/11. */public class FragmentPagerActivity extends FragmentActivity {    private ViewPager mViewPager;    private int startX;    private int mCurrentPage;    private boolean onAnimator;    private int mSlop;    private ArrayList<BaseFragment> mFragments;    private MyFragmentAdapter fragmentAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_pager);        mSlop = ViewConfiguration.get(this).getScaledTouchSlop();        AFragment aFragment = new AFragment();        BFragment bFragment = new BFragment();        CFragment cFragment = new CFragment();        mFragments = new ArrayList<>();        mFragments.add(aFragment);        mFragments.add(bFragment);        mFragments.add(cFragment);        FragmentManager manager = getSupportFragmentManager();        fragmentAdapter = new MyFragmentAdapter(manager);        mViewPager = (ViewPager) findViewById(R.id.vp);        mViewPager.setAdapter(fragmentAdapter);        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {            @Override            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {            }            @Override            public void onPageSelected(int position) {                mCurrentPage = position;            }            @Override            public void onPageScrollStateChanged(int state) {            }        });        mViewPager.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                int action = event.getAction();                switch (action) {                    case MotionEvent.ACTION_DOWN:                        break;                    case MotionEvent.ACTION_MOVE:                        if(startX == 0) {                            startX = (int) event.getRawX();                        }                        int endX = (int) event.getRawX();                        if(mCurrentPage == 0 ) {                            if(endX - startX > mSlop) {                                onAnimator = true;                            }                            if(startX < endX && onAnimator) {                                BaseFragment item = fragmentAdapter.getItem(mCurrentPage);                                ObjectAnimator.ofFloat(item.mRootView, "rotationY", (endX - startX) * 0.025f).setDuration(0).start();                            }                        } else if(mCurrentPage == mFragments.size() -1) {                            if(startX - endX > mSlop) {                                onAnimator = true;                            }                            if(startX > endX && onAnimator) {                                BaseFragment item = fragmentAdapter.getItem(mCurrentPage);                                ObjectAnimator.ofFloat(item.mRootView , "rotationY", (endX - startX) * 0.025f).setDuration(0).start();                            }                        }                        break;                    case MotionEvent.ACTION_UP:                    case MotionEvent.ACTION_CANCEL:                        BaseFragment item = fragmentAdapter.getItem(mCurrentPage);                        ObjectAnimator.ofFloat(item.mRootView, "rotationY", 0).setDuration(250).start();                        startX = 0;                        onAnimator= false;                        break;                }                return onAnimator;            }        });    }    class MyFragmentAdapter extends FragmentPagerAdapter {        public MyFragmentAdapter(FragmentManager fm) {            super(fm);        }        @Override        public BaseFragment getItem(int position) {            return mFragments.get(position);        }        @Override        public int getCount() {            return mFragments.size();        }    }}

五、写在后面

这篇博客到底算是什么呢,我也不知道,不过在网上找了好久还真找不到类似的……
等什么时候说不定会抽成一个自定义控件,像jazzViewPager一样可以设置各种动画。
如果有什么好的建议可以直接留言~

Demo已经上传完毕,PagerAdapter和FragmentPagerAdapter的都有,欢迎下载学习交流。http://download.csdn.net/detail/u010386612/9399487

1 0
原创粉丝点击