视差动画引导界面



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

效果图

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

思路:

  • 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.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;
      }
  }

你可能感兴趣的:(动画,android,viewpager,布局,界面)