效果图:
实现思路
整个滑动页面是个ViewPager,Viewpager中填充多个的Fragment,手指从右向左滑的时候,当前的Fragment为出去的Fragment,当前Fragment右边相邻的Fragment为进入的Fragment,平行动画其实就是进入的Fragment中的各个子View按住手指滑动的不同倍数平移的,考虑到每个Fragment的子View大多都是系统的View,并且为了方便后期拓展,这里通过自定义的Inflater然后扩展系统View的属性,给系统View扩展一些缩放平移倍数之类的属性。
布局
首先是布局,布局很简单,自定义了一个FrameLayout,然后FrameLayout只有一个子元素Viewpager,然后有一个ImageView,用来展示中间步行的小人,小人的运动是监听ViewPager的滑动状态,如果正在滑动,播放ImageView的帧动画,小人就动起来。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.itzb.anim.splash.ParallaxContainer
android:id="@+id/parallax_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_man"
android:layout_width="67dp"
android:layout_height="202dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:background="@drawable/intro_item_manrun_1" />
</RelativeLayout>
自定义Inflater
自定义ParallaxLayoutInflater继承自LayoutInflater,在构造方法中传入要加载的目标Fragment,并在构造方法中监听view被填充,然后自定义Inflater必须实现cloneInContext方法
public class ParallaxLayoutInflater extends LayoutInflater {
private static final String TAG = "ParallaxLayoutInflater";
private ParallaxFragment fragment;
protected ParallaxLayoutInflater(LayoutInflater original, Context newContext, ParallaxFragment fragment) {
super(original, newContext);
this.fragment = fragment;
//监听view被填充
setFactory2(new ParallaxFactory(this));
}
//重写LayoutInflater必须重写此方法
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new ParallaxLayoutInflater(this, newContext, fragment);
}
}
监听view被填充传入实现了Factory2接口的实例,这里我们自定义了ParallaxFactory,并实现onCreateView方法,其中两个我们只需要实现4个参数的onCreateView方法,另一个默认实现即可。其实这里onCreateView我们主要的目的是拿到自定义属性,我们在系统View的xml中添加的属性需要在这里读出来,并且还要读出来非系统View的自定义属性,首先是定义本次需要永用到的自定义属性
//自定义的属性
int[] attrIds = {
R.attr.a_in,
R.attr.a_out,
R.attr.x_in,
R.attr.x_out,
R.attr.y_in,
R.attr.y_out
};
然后对于onCreateView(View parent, String name, Context context, AttributeSet attrs)中的参数name,对于系统View,他是缩写,例如TextView的name就是TextView,自定义View的name这里则是完整的包名,因此这里创建View需要根据是否是系统View做不同的处理,返回的View我们可以利用系统的LayoutInflater的inflate方法加载,也可以通过包名反射创建,实际上系统的inflate就是利用反射做的,既然系统都做了,我们就可以直接哪来用。加载完View后我们将View的自定义属性通过一个TAG保存起来
/**
* ParallaxFactory监听View从xml加载到内存
*/
private class ParallaxFactory implements Factory2 {
private LayoutInflater layoutInflater;
//加载的系统View都在这两个包下
private final String[] sClassPrefix = {
"android.widget.",
"android.view."
};
//自定义的属性
int[] attrIds = {
R.attr.a_in,
R.attr.a_out,
R.attr.x_in,
R.attr.x_out,
R.attr.y_in,
R.attr.y_out
};
public ParallaxFactory(LayoutInflater layoutInflater) {
this.layoutInflater = layoutInflater;
}
@SuppressLint("ResourceType")
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = createMyView(name, context, attrs);
if (view != null) {
TypedArray a = context.obtainStyledAttributes(attrs, attrIds);
if (a != null && a.length() > 0) {
//获取自定义属性的值
ParallaxViewTag tag = new ParallaxViewTag();
tag.alphaIn = a.getFloat(0, 0f);
tag.alphaOut = a.getFloat(1, 0f);
tag.xIn = a.getFloat(2, 0f);
tag.xOut = a.getFloat(3, 0f);
tag.yIn = a.getFloat(4, 0f);
tag.yOut = a.getFloat(5, 0f);
view.setTag(R.id.parallax_view_tag, tag);
}
fragment.getViews().add(view);
a.recycle();
}
Log.d(TAG, "onCreateView: ");
return view;
}
private View createMyView(String name, Context context, AttributeSet attrs) {
if (name.contains(".")) {//自定义的空间
return reflectView(name, null, context, attrs);
} else {//系统空间
for (String classPrefix : sClassPrefix) {
View view = reflectView(name, classPrefix, context, attrs);
if (view != null) {
return view;
}
}
}
return null;
}
private View reflectView(String name, String prefix, Context context, AttributeSet attrs) {
try {
//通过系统的inflater创建视图,读取系统的属性
return layoutInflater.createView(name, prefix, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
}
ParallaxContainer
ParallaxContainer是Viewpager的父布局,因此需要在这里对ViewPager初始化,并且我们这这里暴露一个setUp方法,参数传入每一个Fragment的xml布局id,然后我们通过bundle将每个fragment对应的xml的id传给对应的fragment,fragment就可以根据这个xml的id进行加载。最后我们在ParallaxContainer中对Viewpager的滑动进行监听,核心是onPageScrolled,我们需要在这里根据手指滑动的距离对每个View做平移动画,剩下的简单一看就懂
public class ParallaxContainer extends FrameLayout implements ViewPager.OnPageChangeListener {
private List<ParallaxFragment> fragments = new ArrayList<>();
private ImageView ivMain;
private ParallaxPagerAdapter adapter;
public ParallaxContainer(@NonNull Context context) {
this(context, null);
}
public ParallaxContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ParallaxContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setIvMain(ImageView ivMain) {
this.ivMain = ivMain;
}
public void setUp(int... childIds) {
for (int i = 0; i < childIds.length; i++) {
ParallaxFragment parallaxFragment = new ParallaxFragment();
Bundle args = new Bundle();
//Fragment中需要加载的布局文件id
args.putInt("layoutId", childIds[i]);
parallaxFragment.setArguments(args);
fragments.add(parallaxFragment);
}
//初始化ViewPager
ViewPager viewPager = new ViewPager(getContext());
viewPager.setId(R.id.parallax_pager);
SplashActivity activity = (SplashActivity) getContext();
viewPager.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
adapter = new ParallaxPagerAdapter(activity.getSupportFragmentManager(), fragments);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(this);
addView(viewPager, 0);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
ParallaxFragment outFragment = null;
ParallaxFragment inFragment = null;
try {
outFragment = fragments.get(position - 1);
inFragment = fragments.get(position);
} catch (Exception e) {
e.printStackTrace();
}
if (outFragment != null) {
List<View> views = outFragment.getViews();
if (views != null && views.size() > 0) {
for (View view : views) {
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null) {
continue;
}
ViewHelper.setTranslationX(view, (getWidth() - positionOffsetPixels) * tag.xOut);
ViewHelper.setTranslationY(view, (getWidth() - positionOffsetPixels) * tag.yOut);
}
}
}
if (inFragment != null) {
List<View> views = inFragment.getViews();
if (views != null && views.size() > 0) {
for (View view : views) {
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null) {
continue;
}
//退出的fragment中view从原始位置开始向上移动,translationY应为负数
ViewHelper.setTranslationX(view, 0 - positionOffsetPixels * tag.xIn);
ViewHelper.setTranslationY(view, 0 - positionOffsetPixels * tag.yIn);
}
}
}
}
@Override
public void onPageSelected(int position) {
ivMain.setVisibility(position == adapter.getCount() - 1 ? GONE : VISIBLE);
}
@Override
public void onPageScrollStateChanged(int state) {
AnimationDrawable animationDrawable = (AnimationDrawable) ivMain.getBackground();
if (state == SCROLL_STATE_IDLE) {
animationDrawable.stop();
} else if (state == SCROLL_STATE_DRAGGING) {
animationDrawable.start();
}
}
}
ParallaxFragment
fragment可以拿到ParallaxContainer中传过来的xml的id,然后利用已定义的Inflater填充即可
public class ParallaxFragment extends Fragment {
private List<View> views = new ArrayList<>();
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
int layoutId = getArguments().getInt("layoutId");
ParallaxLayoutInflater layoutInflater = new ParallaxLayoutInflater(inflater, getActivity(), this);
return layoutInflater.inflate(layoutId, null);
}
public List<View> getViews() {
return views;
}
}
添加页面
到这我们只需要在activity中调用ParallaxContainer的setUp方法传入需要加载的fragment的xml数组即可,并且后期扩展页面我们只需要修改xml数组即可,可扩展性很强
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ParallaxContainer container = (ParallaxContainer) findViewById(R.id.parallax_container);
container.setUp(new int[]{
R.layout.view_intro_1,
R.layout.view_intro_2,
R.layout.view_intro_3,
R.layout.view_intro_4,
R.layout.view_intro_5,
R.layout.view_intro_6,
R.layout.view_intro_7,
R.layout.view_login
});
ImageView iv_man = findViewById(R.id.iv_man);
iv_man.setBackgroundResource(R.drawable.man_run);
container.setIvMain(iv_man);
}
}