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不管怎麼變化,我們只需要的簡單的幾步就能實現所需要的效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章