实现带3D弹性效果的ViewPager(非自定义控件)
来源:互联网 发布:模糊逻辑算法 编辑:程序博客网 时间:2024/05/16 08:20
一、前言
最近有朋友问我,这种效果是怎么做出来的……
csdn只能上传小于2M的图片,只好划的快一点了。
效果就是,在第一页和最后一页的时候,继续滑动会有个3D的旋转效果。
第一反应就是jazzViewpager,叫朋友去试试看。结果他说不行,我也没去试,估计jazzViewPager是每个页面都会有动画……
然后我用了一个很笨的方法,就是监听touch事件,用属性动画搞定。
二、实现思路
- 判断当前的pager是否是第一个或者最后一个。
- 监听滑动事件,根据手指的位移一直改变View的角度。其实重点就在这里了=。=
- 当手指松开的时候,用属性动画将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
- 实现带3D弹性效果的ViewPager(非自定义控件)
- Android自定义控件带弹性
- 自定义3D效果的轮转控件
- ViewPager的弹性缩回效果
- 3D效果的ViewPager
- 使用自定义RadioButton和ViewPager实现TabHost效果和带滑动的页卡效果。
- 自定义ScrollView实现弹性效果
- Android自定义ViewPager:水平滑动弹性效果,侧滑刷新加载的ViewPager
- android 自定义控件实现3D画廊效果
- 实现3D翻转效果的仿ViewPager
- 复杂自定义控件---自定义ViewPager的实现
- 【安卓自定义控件】自定义ViewGroup实现透明背景的ViewPager效果
- ViewPager+Fragmet 实现3D滑动效果
- Android Viewpager实现3D画廊效果
- android viewpager实现3d画廊效果
- Android 实现ListView 3D效果 - 2 - 弹性滚动,Fling
- Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果
- Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果
- UIBarItem
- VirtualBox虚拟机快速入门(3):使用技巧【注册】【导入】
- 关于 NSURLSession 的上传和下载
- 【Android】java.lang.AssertionError use looper thread, must call Looper.prepare() first!异常分析
- Java文件操作大全
- 实现带3D弹性效果的ViewPager(非自定义控件)
- SugarCRM源码分析之加载配置
- Keychain
- java数组转换字符串
- 一种月计划模版
- MediaExtractor的seekTo方法精确定位到指定帧
- ajax(四) Ajax应用:验证用户名是否可用
- 51单片机控制的收音机(带串口,遥控,芯片89S52+LC72131+LA1845N)
- 关于encodeURLComponent引申出来的杂谈