Android仿小红书启动页平行动画

实现效果

 

需要注意的:

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不管怎么变化,我们只需要的简单的几步就能实现所需要的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值