自定义Inflater实现系统View自定义属性,实现小红书Parallax平行动画

效果图:

实现思路

整个滑动页面是个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);
    }
}

 

Demo:https://github.com/987570437/SplashAnimation

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值