最近有朋友问我,这种效果是怎么做出来的……
csdn只能上传小于2M的图片,只好划的快一点了。
效果就是,在第一页和最后一页的时候,继续滑动会有个3D的旋转效果。
第一反应就是jazzViewpager,叫朋友去试试看。结果他说不行,我也没去试,估计jazzViewPager是每个页面都会有动画……
然后我用了一个很笨的方法,就是监听touch事件,用属性动画搞定。
没有自定义一个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;
}
}
}
将上面那个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