实现效果
需要注意的:
view.setTag()和view.getTag()
View中的setTag(Object)表示给View添加一个格外的数据,以后可以用getTag()将这个数据取出来。
实现思路:
通过ViewPager
加载Fragment
,在Fragment
中的系统控件中加入我们的自定义属性。然后我们通过解析自定义属性来实现平行动画。
首先我们可以在Fragment
的系统的控件中加入我们自定义的属性。
<ImageView app:x_in="0.8" app:x_out="0.8" />
那么现在要解决的一个问题就是如何获取到系统控件中自定义属性的值。
我们可以通过继承LayoutInflater
,调用setFactory2(new ParallaxFactory(this))
,实现我们自己的ParallaxLayoutInflater
类,然后再自定义一个类ParallaxFactory
实现Factory2
接口,在这个ParallaxFactory
类中我们可以获取到系统控件中的所有属性和值,包括我们自定义的属性。
获取到自定义属性的值之后,先通过setTag()来保存view自定义属性的值,然后将fragment中的所有View保存起来,最后在viewPager的onPageScrolled()方法中获取到每个View的自定义属性的值,从而实现平行动画。
1.定义一个ParallaxViewTag类,将我们给View传递的数据封装起来
public class ParallaxViewTag {
protected int index;
protected float xIn;
protected float xOut;
protected float yIn;
protected float yOut;
protected float alphaIn;
protected float alphaOut;
@NonNull
@Override
public String toString() {
return "ParallaxViewTag [index=" + index + ", xIn=" + xIn + ", xOut="
+ xOut + ", yIn=" + yIn + ", yOut=" + yOut + ", alphaIn="
+ alphaIn + ", alphaOut=" + alphaOut + "]";
}
}
2.定义Fragment来加载布局文件
public class ParallaxFragment extends Fragment {
//在此Fragment上实现所有的视差动画
private List<View> parallaxViews = new ArrayList<>();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//通过Bundle获取布局文件Id
Bundle args = getArguments();
int layoutId = args.getInt("layoutId");
ParallaxLayoutInflater inflater1 = new ParallaxLayoutInflater(inflater, getActivity(), this);
return inflater1.inflate(layoutId, null);
}
public List<View> getParallaxViews(){
return parallaxViews;
}
}
3.定义ViewPager的适配器Adapter
public class ParallaxPagerAdapter extends FragmentPagerAdapter {
private List<ParallaxFragment> fragments;
public ParallaxPagerAdapter(FragmentManager fragmentManager, List<ParallaxFragment> fragments){
super(fragmentManager);
this.fragments = fragments;
}
@NonNull
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
}
4.定义我们自己的ParallaxLayoutInflater,继承系统的LayoutInflater,在这个类中我们要通过监听系统解析xml的过程,来将我们自己定义的属性的值提取出来,然后通过setTag()方式保存起来,以备后面调用。
关键代码:
class ParallaxFactory implements Factory2{
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};
private LayoutInflater inflater;
public ParallaxFactory(LayoutInflater inflater){
this.inflater = inflater;
}
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
View view =createMyView(name, context, attrs);
if (view != null){
TypedArray array = context.obtainStyledAttributes(attrs, attrIds);
if (array != null && array.length() > 0){
//获取自定义的属性
ParallaxViewTag tag = new ParallaxViewTag();
tag.alphaIn = array.getFloat(0, 0f);
tag.alphaOut = array.getFloat(1, 0f);
tag.xIn = array.getFloat(2, 0f);
tag.xOut = array.getFloat(3, 0f);
tag.yIn = array.getFloat(4, 0f);
tag.yOut = array.getFloat(5, 0f);
view.setTag(R.id.parallax_view_tag,tag);
}
fragment.getParallaxViews().add(view);
array.recycle();
}
Log.i(TAG, "onCreateView: " + view);
return view;
}
@Nullable
@Override
public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
return null;
}
private View createMyView(String name, Context context, AttributeSet attributeSet){
if (name.contains(".")){
//自定义的控件
return reflectView(name, null, context, attributeSet);
}else {
//轮训我们在系统控件中定义的属性
for (String prefix:sClassPrefix) {
View view = reflectView(name, prefix, context, attributeSet);
if (view != null)
{
return view;
}
}
}
return null;
}
private View reflectView(String name, String prefix, Context context, AttributeSet attrs){
try {
return inflater.createView(name, prefix, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
5.提供一个ParallaxContainer类来控制所有的fragment的加载和动画
导入ftagment数组关键代码:
public void setUp(int... childIds){
//fragments数组
fragments = new ArrayList<ParallaxFragment>();
for (int i = 0; i < childIds.length; i++) {
ParallaxFragment fragment = new ParallaxFragment();
//Fragment中需要加载的布局文件id
Bundle args = new Bundle();
args.putInt("layoutId", childIds[i]);
fragment.setArguments(args);
fragments.add(fragment);
}
ViewPager vp = new ViewPager(getContext());
vp.setId(R.id.parallax_pager);
//实例化适配器
SplashActivity activity = (SplashActivity) getContext();
adapter = new ParallaxPagerAdapter(activity.getSupportFragmentManager(), fragments);
// vp.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
vp.setAdapter(adapter);
vp.setOnPageChangeListener(this);
addView(vp, 0);
}
//控制动画关键代码
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int containerWidth = getWidth();
ParallaxFragment outFragment = null;
try {
outFragment = fragments.get(position - 1);
} catch (Exception e) {}
ParallaxFragment inFragment = null;
try {
inFragment = fragments.get(position);
} catch (Exception e) {}
if (outFragment != null){
List<View> outViews = outFragment.getParallaxViews();
if (outViews != null){
for (View view:outViews) {
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null){
continue;
}
ViewHelper.setTranslationX(view, (containerWidth - positionOffsetPixels) * tag.xIn);
ViewHelper.setTranslationY(view, (containerWidth - positionOffsetPixels) * tag.yIn);
}
}
}
if (inFragment != null){
List<View> inViews = inFragment.getParallaxViews();
if (inViews != null){
for (View view:inViews) {
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null){
continue;
}
//仔细观察退出的fragment中view从原始位置开始向上移动,translationY应为负数
ViewHelper.setTranslationY(view, 0 - positionOffsetPixels * tag.yOut);
ViewHelper.setTranslationX(view, 0 - positionOffsetPixels * tag.xOut);
}
}
}
}
通过在系统控件中加入自定义属性来实现平行动画,扩展性好,启动页的UI不管怎么变化,我们只需要的简单的几步就能实现所需要的效果。