自定義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

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