视差动画引导界面。视差动画的核心是控制视图的动画的方向和速度。
录屏的效果不是很好,真实手机上运行效果很不错。
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,可以滑动了。效果图如下:
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中获取,怎么做到呢?可以通过下面的三步。
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; } } |