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

龙旋

共 6863字,需浏览 14分钟

 ·

2021-10-24 14:33

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


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


先上图:


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


attrs文件

//进入的时候透明度//出去的时候透明度//进入的时候x方向的速度//出去的时候x方向的速度//进入的时候Y方向的速度出去的时候Y方向的速度


然后是布局文件其中的控件,添加我们自定义的属性
    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

到这里就结束啦。
浏览 92
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报