android viewpager 无限循环_Android仿小红书启动页平行动画

其实之前在很多APP的引导页上都看到过这个效果,通过速度不同给人带来视觉差,感觉很炫酷。

通过网易云的课做了一个demo,记录一下。

先上图

6dd41b57cc4c259a8b960d2609bbe42a.gif

首先来梳理一下实现的思路,其实就是viewpager+fragment,只不过这个fragment里的控件,会根据viewpager的滑动产生不同的速度的位移。从架构的角度来考虑的话,我们要做到可以随时添加fragment,同时可以随时添加fragment里的控件,控件的速度可以设置。所以要给系统控件来添加自定义属性。

attrs文件

<attr name="a_in" format="float" />//进入的时候透明度<attr name="a_out" format="float" />//出去的时候透明度<attr name="x_in" format="float" />//进入的时候x方向的速度<attr name="x_out" format="float" />//出去的时候x方向的速度<attr name="y_in" format="float" />//进入的时候Y方向的速度<attr name="y_out" format="float" />出去的时候Y方向的速度
然后是布局文件其中的控件,添加我们自定义的属性
<ImageView    android:id="@+id/iv_0"    android:layout_width="103dp"    android:layout_height="19dp"    android:layout_centerInParent="true"    android:src="@drawable/intro1_item_0"    app:x_in="1.2"    app:x_out="1.2" />

自定义FrameLayout

为了方便管理fragment和viewpager,我们自定义一个frameLayout, 实现Viewpager的OnPageChangeListener,提供一个setUp方法, 来添加布局文件,从而新建数量相同的fragment。
public void setUp(int... childIds) {    //fragment集合    fragments = new ArrayList();    for (int i = 0; i < childIds.length; i++) {        ParallaxFragment fragment = new ParallaxFragment();        //通过bundle传递参数给fragment        Bundle bundle = new Bundle();        bundle.putInt("layoutId", childIds[i]);        fragment.setArguments(bundle);        fragments.add(fragment);    }    ViewPager vp = new ViewPager(getContext());    vp.setId(R.id.parallax_pager);//从value里的ids拿的    SplashActivity activity = (SplashActivity) getContext();    parallaxPagerAdapter = new ParallaxPagerAdapter(activity.getSupportFragmentManager(), fragments);    vp.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));    vp.setAdapter(parallaxPagerAdapter);    vp.setOnPageChangeListener(this);    addView(vp,0);}

自定义Fragment

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {    //获取到布局文件id    int rootViewId = getArguments().getInt("layoutId");    //自定义LayoutInflater来处理里面的控件    ParallaxLayoutInflater parallaxLayoutInflater = new ParallaxLayoutInflater(inflater,getActivity(),this);    View rootView = parallaxLayoutInflater.inflate(rootViewId, null);    return rootView;}

自定义LayoutInflater

对于我们自定义的fragment ,我们同样需要通过自定义layoutInflater来对控件的布局进行管理,自定义的layoutInflater里最重要的就是要实现setFactory2这个方法,自定义一个Factory,通过里面的onCreateView方法,来获取控件的自定义属性,再把所有的自定义属性都通过ParallaxTag添加到View的tag里。这里要注意区分是系统控件还是自定义控件,也要考虑兼容自定义控件。
class ParallaxFactory implements Factory2 {    private LayoutInflater layoutInflater;    //系统控件的前缀    private 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;    }    /**     * 反射机制     *     * @param parent  顶级容器     * @param name    控件名字(像RelativeLayout,ImageView)如果是自定义控件的话  返回的是全路径名字     * @param context     * @param attrs   控件的属性(width,height)     * @return     */    @Override    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {        View view = null;        view = createMyView(name, context, attrs);        if (view != null) {            //attrs 控件的所有属性,attrIds 是想要获取的控件的属性            TypedArray array = context.obtainStyledAttributes(attrs, attrIds);            if (array != null && array.length() > 0) {                ParallaxViewTag viewTag = new ParallaxViewTag();                viewTag.alphaIn = array.getFloat(0, 0f);                viewTag.alphaOut = array.getFloat(1, 0f);                viewTag.xIn = array.getFloat(2, 0f);                viewTag.xOut = array.getFloat(3, 0f);                viewTag.yIn = array.getFloat(4, 0f);                viewTag.yOut = array.getFloat(5, 0f);                view.setTag(R.id.parallax_view_tag, viewTag);            }            fragment.getParallaxViews().add(view);            array.recycle();        }        return view;    }    /**     * 创建view     * @param name     * @param context     * @param attrs     * @return     */    private View createMyView(String name, Context context, AttributeSet attrs) {        //如果是系统的控件,不会有.  如果是自定义控件的话  含有.        if (name.contains(".")) {            return reflectView(name, null, attrs);        } else {            //循环两个包            for (String prefix : sClassPrefix) {                View view = reflectView(name, prefix, attrs);                if (view != null) {                    return view;                }            }        }        return null;    }    /**     * @param name   控件名字     * @param prefix 控件前缀     * @param attrs  控件属性     * @return     */    private View reflectView(String name, String prefix, AttributeSet attrs) {        try {            //通过系统的layoutInflater创建视图,读取系统属性            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;    }}
最后我们就要实现,根据viewpager的滑动来控制Fragment里不同控件的不同移动速度,这里要区分fragment是进入状态还是退出状态
@Override    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        //动画操作        int containerWidth = getWidth();        //获取到退出        ParallaxFragment outFragment = null;        try {            outFragment = fragments.get(position - 1);        } catch (Exception e) {            e.printStackTrace();        }        //获取到进入的页面        ParallaxFragment inFragment = null;        try {            inFragment = fragments.get(position);        } catch (Exception e) {        }        if (outFragment != null) {            //获取Fragment上所有的视图,实现动画效果            List inViews = outFragment.getParallaxViews();//            动画            if (inViews != null) {                for (View view : inViews) {                    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 outViews = inFragment.getParallaxViews();            if (outViews != null) {                for (View view : outViews) {                    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);                }            }        }    }
最后总结一下的话,其实就是对于自定义控件的实现,要做到熟练,考虑实现过程的时候,要尽量做到可扩展性,而不是单独实现功能就可以。 源码地址: https://github.com/wangxueshen/NetEaseDemo 到这里就结束啦. 往期精彩回顾:
  • Android实现短信验证码自动填充功能

  • Android仿echo精美弹幕功能

  • Android实现头像重叠排列功能

  • Android仿QQ个性标签功能

  • Android仿QQ侧滑删除的功能

cd808351496f61c8491954ed3f05cccc.png

03644c0a3d06409a9d07eb274d776cdd.png

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值