视差动画引导界面

来源:互联网 发布:破解版软件大全 编辑:程序博客网 时间:2024/05/16 04:37


视差动画引导界面。视差动画的核心是控制视图的动画的方向和速度。

效果图

录屏的效果不是很好,真实手机上运行效果很不错。
视差动画视差动画

思路:

  • ViewPager每一页都包含视图(Fragment)
  • 翻页过程中,控制视图的滑动

    数据准备

    新建ParallaxContainer继承自FrameLayout,在该文件中指定引导页的所有页面布局文件。
    package me.chenfuduo.parallaxsplash;import android.content.Context;import android.os.Bundle;import android.support.v4.app.Fragment;import android.support.v4.view.ViewPager;import android.util.AttributeSet;import android.view.ViewGroup;import android.view.ViewParent;import android.widget.FrameLayout;import java.util.ArrayList;import java.util.List;/** * Created by Administrator on 2015/5/27. * 引导页的最外层布局 */public class ParallaxContainer extends FrameLayout {    private List<ParallaxFragment> fragments;    public ParallaxContainer(Context context) {        this(context, null);    }    public ParallaxContainer(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public ParallaxContainer(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    /**     * 指定引导页的所有页面布局文件     *     * @param childIds     */    public void setUp(int... childIds) {        //根据布局文件数组,初始化所有的Fragment        fragments = new ArrayList<>();        for (int i = 0; i < childIds.length; i++) {            ParallaxFragment f = new ParallaxFragment();            Bundle args = new Bundle();            //页面索引            args.putInt("index", i);            //Fragment中需要加载的布局文件id            args.putInt("layoutId", childIds[i]);            f.setArguments(args);            fragments.add(f);        }        //实例化适配器        MainActivity activity = (MainActivity) getContext();        ParallaxPagerAdapter adapter = new ParallaxPagerAdapter(activity.getSupportFragmentManager(),fragments);        //实例化ViewPager        ViewPager vp = new ViewPager(getContext());        vp.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        vp.setId(R.id.parallax_pager);        //绑定        vp.setAdapter(adapter);        addView(vp,0);    }}

上面的代码中,需要留意的是ViewPager的setId(int)方法,详情见源代码。
在MainActivity中调用:

package me.chenfuduo.parallaxsplash;import android.os.Bundle;import android.support.v4.app.FragmentActivity;/** * */public class MainActivity extends FragmentActivity {    @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_login,        });    }}

初始化Fragment和适配器:

package me.chenfuduo.parallaxsplash;import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;/** * Created by Administrator on 2015/5/27. */public class ParallaxFragment extends Fragment {    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {        Bundle args = getArguments();        int layoutId = args.getInt("layoutId");        int index = args.getInt("index");        View view = inflater.inflate(layoutId,container,false);        return view;    }}

package me.chenfuduo.parallaxsplash;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentPagerAdapter;import java.util.List;/** * Created by Administrator on 2015/5/27. */public class ParallaxPagerAdapter extends FragmentPagerAdapter {    private final List<ParallaxFragment> fragments;    public ParallaxPagerAdapter(FragmentManager fm,List<ParallaxFragment> fragments) {        super(fm);        this.fragments = fragments;    }    @Override    public Fragment getItem(int position) {        return fragments.get(position);    }    @Override    public int getCount() {        return fragments.size();    }}

此时运行,OK,可以滑动了。效果图如下:
1.gif1.gif

自定义属性

ok,以上实现了最基本的引导界面。
分析下每个Fragment的布局,这里只举一个,看下代码:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="fill_parent"    android:layout_height="fill_parent" >    <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" />    <ImageView        android:id="@+id/iv_1"        android:layout_width="181dp"        android:layout_height="84dp"        android:layout_alignParentLeft="true"        android:layout_alignParentTop="true"        android:layout_marginLeft="13dp"        android:layout_marginTop="60dp"        android:src="@drawable/intro1_item_1"        app:x_in="0.8"        app:x_out="0.8" />    <ImageView        android:id="@+id/iv_2"        android:layout_width="143dp"        android:layout_height="58dp"        android:layout_alignParentRight="true"        android:layout_alignParentTop="true"        android:layout_marginTop="109dp"        android:src="@drawable/intro1_item_2"        app:x_in="1.1"        app:x_out="1.1" />    <ImageView        android:id="@+id/iv_3"        android:layout_width="48dp"        android:layout_height="48dp"        android:layout_alignParentRight="true"        android:layout_alignParentBottom="true"        android:layout_marginRight="40dp"        android:layout_marginBottom="185dp"        android:src="@drawable/intro1_item_3"        app:x_in="0.8"        app:x_out="0.8"        app:a_in="0.8"        app:a_out="0.8" />    <ImageView        android:id="@+id/iv_4"        android:layout_width="fill_parent"        android:layout_height="128dp"        android:layout_alignParentBottom="true"        android:layout_marginBottom="29dp"        android:background="@drawable/intro1_item_4"        app:a_in="0.8"        app:a_out="0.8"        app:x_in="0.8"        app:x_out="0.8" />    <ImageView        android:id="@+id/iv_5"        android:layout_width="260dp"        android:layout_height="18dp"        android:layout_alignParentBottom="true"        android:layout_alignParentLeft="true"        android:layout_marginBottom="16dp"        android:layout_marginLeft="15dp"        android:src="@drawable/intro1_item_5"        app:a_in="0.9"        app:a_out="0.9"        app:x_in="0.9"        app:x_out="0.9" />    <ImageView        android:id="@+id/iv_6"        android:layout_width="24dp"        android:layout_height="116dp"        android:layout_alignParentBottom="true"        android:layout_alignParentLeft="true"        android:layout_marginBottom="35dp"        android:layout_marginLeft="46dp"        android:src="@drawable/intro1_item_6"        app:x_in="0.6"        app:x_out="0.6" />    <ImageView        android:id="@+id/iv_7"        android:layout_width="45dp"        android:layout_height="40dp"        android:layout_alignParentBottom="true"        android:layout_alignParentLeft="true"        android:layout_marginBottom="23dp"        android:layout_marginLeft="76dp"        android:src="@drawable/intro1_item_7"        app:a_in="0.3"        app:a_out="0.3"        app:x_in="0.5"        app:x_out="0.5" /></RelativeLayout>

我们看到了很多自定义的属性,这些属性是控制在x轴和y轴的速度还有透明度的大小变化,其中自定义的属性的代码为:

<?xml version="1.0" encoding="utf-8"?><resources>    <attr name="a_in" format="float" />    <attr name="a_out" format="float" />    <attr name="x_in" format="float" />    <attr name="x_out" format="float" />    <attr name="y_in" format="float" />    <attr name="y_out" format="float" /></resources>

获取自定义的属性

那么现在问题来了,如何在Fragment中获取这些自定义的属性?回忆我们平时怎么获取自定义属性的,那时候我们都是在自定义View的构造器中去获取,但是我们现在缺失要在Fragment中获取,怎么做到呢?可以通过下面的三步。

  • 布局加载器将布局加载进来
  • 解析创建布局上所有的视图
  • 我们自己搞定创建视图的过程
  • 获取视图相关的自定义属性的值
    我们首先自定义一个类,让他继承自LayoutInflater,如下:
    package me.chenfuduo.parallaxsplash;import android.content.Context;import android.view.LayoutInflater;/** * Created by Administrator on 2015/5/27. */public class ParallaxLayoutInflater extends LayoutInflater {    protected ParallaxLayoutInflater(Context context) {        this(null,context);    }    protected ParallaxLayoutInflater(LayoutInflater original, Context newContext) {        super(original, newContext);    }    @Override    public LayoutInflater cloneInContext(Context newContext) {        return null;    }}

接下来我们在ParallaxFragment去使用我们自己定义的LayoutInflater。如下:

package me.chenfuduo.parallaxsplash;import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;/** * Created by Administrator on 2015/5/27. */public class ParallaxFragment extends Fragment {    @Override    public View onCreateView(LayoutInflater original, ViewGroup container, Bundle savedInstanceState) {        Bundle args = getArguments();        int layoutId = args.getInt("layoutId");        int index = args.getInt("index");        //1.布局加载器将布局加载进来了        //2.解析创建布局上所有的视图        //3.我们自己搞定创建视图的过程        //4.获取视图相关的自定义属性的值        ParallaxLayoutInflater inflater = new ParallaxLayoutInflater(original,getActivity());        View view = inflater.inflate(layoutId,container,false);        return view;    }}

下面的这句话很重要:如果你想为你自己的View创建另一个LayoutInflater,可以使用LayoutInflater.Factory.首先调用cloneInContext(Context)函数赋值一个已经存在的ViewFactory,然后再调用setFactory(LayoutInflater.Factory)方法。

package me.chenfuduo.parallaxsplash;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.ContextThemeWrapper;import android.view.LayoutInflater;import android.view.View;/** * Created by Administrator on 2015/5/27. */public class ParallaxLayoutInflater extends LayoutInflater {    private ParallaxFragment fragment;    protected ParallaxLayoutInflater(LayoutInflater original, Context newContext,ParallaxFragment fragment) {        super(original, newContext);        this.fragment = fragment;        //重新设置布局加载器的工厂        //工厂用于创建文件中所有的视图        setFactory(new ParallaxFactory(this));    }    /**     * Create a copy of the existing LayoutInflater object, with the copy     * pointing to a different Context than the original.  This is used by     * {@link ContextThemeWrapper} to create a new LayoutInflater to go along     * with the new Context theme.     *     * @param newContext The new Context to associate with the new LayoutInflater.     *                   May be the same as the original Context if desired.     * @return Returns a brand spanking new LayoutInflater object associated with     * the given Context.     */    @Override    public LayoutInflater cloneInContext(Context newContext) {        return new ParallaxLayoutInflater(this, newContext,fragment);    }    class ParallaxFactory implements LayoutInflater.Factory {        private LayoutInflater inflater;        private final String[] sClassPrefix = {                "android.widget.",                "android.view."        };        public ParallaxFactory(LayoutInflater layoutInflater) {            this.inflater = layoutInflater;        }        /**         * 自定义视图创建的过程         *         * @param name         * @param context         * @param attrs         * @return         */        @Override        public View onCreateView(String name, Context context, AttributeSet attrs) {            //android.widget.TextView;  TextView的prefix就是android.widget            //当然在xml文件譬如ImageView和TextView等等有prefix,而其他的可能没有            //没有的情况:比如我们自定义View,然后我们拷贝全路径到xml文件中            //这种情况下没有prefix            //==============================            //总结            //1.自定义控件不需要前缀            //2.系统视图需要加上前缀(主要是两个,一个是android.view一个是android.widget)            View view = null;            if (view == null) {                view = createViewOrFailQuitely(name, context, attrs);            }            //实例化完成            if (view != null) {                //获取自定义属性,通过标签关联到视图上                setViewTag(view, context, attrs);                fragment.getParallaxViews().add(view);            }            return view;        }        private void setViewTag(View view, Context context, AttributeSet attrs) {            //所有自定义的属性            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            };            //获取(这种方式是不是和前面我们获取自定义属性的方式不同!!!)            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);               // tag.index                view.setTag(R.id.parallax_view_tag,tag);            }            array.recycle();        }        private View createViewOrFailQuitely(String name, String prefix, Context context, AttributeSet attrs) {            try {                return inflater.createView(name, prefix, attrs);            } catch (Exception e) {                e.printStackTrace();                return null;            }        }        private View createViewOrFailQuitely(String name, Context context, AttributeSet attrs) {            //自定义控件不需要前缀            if (name.contains(".")) {                createViewOrFailQuitely(name, null, context, attrs);            }            //系统视图需要加上前缀            for (String prefix : sClassPrefix) {                View view = createViewOrFailQuitely(name, prefix, context, attrs);                if (view != null) {                    return view;                }            }            return null;        }    }}

ParallaxFragment的代码:

package me.chenfuduo.parallaxsplash;import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import java.util.ArrayList;import java.util.List;/** * Created by Administrator on 2015/5/27. */public class ParallaxFragment extends Fragment {    //此Fragment上所有需要实现视差动画的视图    private List<View> parallaxViews = new ArrayList<>();    @Override    public View onCreateView(LayoutInflater original, ViewGroup container, Bundle savedInstanceState) {        Bundle args = getArguments();        int layoutId = args.getInt("layoutId");        int index = args.getInt("index");        //1.布局加载器将布局加载进来了        //2.解析创建布局上所有的视图        //3.我们自己搞定创建视图的过程        //4.获取视图相关的自定义属性的值        ParallaxLayoutInflater inflater = new ParallaxLayoutInflater(original,getActivity(),this);        View view = inflater.inflate(layoutId,container,false);        return view;    }    public List<View> getParallaxViews() {        return parallaxViews;    }}

这里需要注意的是ParallaxLayoutInflater这个类,这个实现了很多之前没有见过的。

添加动画

添加动画主要在ViewPager的监听器中完成。

@Override  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {      containerWidth = getWidth();      //在翻页的过程中,不断根据视图的标签中对应的动画参数,改变视图的位置或者透明度      //获取到进入的页面      ParallaxFragment inFragment = null;      try {          inFragment = fragments.get(position - 1);      } catch (Exception e) {          e.printStackTrace();      }      //获取到退出的页面      ParallaxFragment outFragment = null;      try {          outFragment = fragments.get(position);      } catch (Exception e) {          e.printStackTrace();      }      if (inFragment != null) {          //获取Fragment上所有的视图,实现动画效果          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;                  }                  //left                  ViewHelper.setTranslationX(view, (containerWidth - positionOffsetPixels) * tag.xIn);                  //top                  ViewHelper.setTranslationY(view, (containerWidth - positionOffsetPixels) * tag.yIn);              }          }      }      if (outFragment != null) {          //获取Fragment上所有的视图,实现动画效果          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, 0 - positionOffsetPixels * tag.xOut);                  ViewHelper.setTranslationY(view, 0 - positionOffsetPixels * tag.yOut);              }          }      }  }  @Override  public void onPageSelected(int position) {      if (position == adapter.getCount() - 1){          iv.setVisibility(INVISIBLE);      }else{          iv.setVisibility(VISIBLE);      }  }  @Override  public void onPageScrollStateChanged(int state) {      final AnimationDrawable animationDrawable = (AnimationDrawable) iv.getBackground();      switch (state) {          case ViewPager.SCROLL_STATE_IDLE:              animationDrawable.stop();              break;          case ViewPager.SCROLL_STATE_DRAGGING:              animationDrawable.start();              break;          case 2:              animationDrawable.stop();              break;      }  }

0 0