Android高级UI开发(三十)让系统控件识别自定义属性的第二种方法:视差动画框架实例
上一篇已经讲了让系统控件如Imageview识别自定义属性的方法一: 自定义LinearLayout,在addview的时候,给每一个系统控件外层再包裹一个自定义VIEWGROUP,然后这个VIEWGROUP来识别自定义属性并执行动画。今天我们讲第二种方法,与第一种方法有点雷同,就是在解析XML中的系统控件时,解析出属性及属性值,把这个控件如Imageview上的自定义属性解析出来封装在一个TAG对象里,然后使用view.setTag方法将自定义属性与这个系统控件关联起来。当我们执行动画时,可以使用view.getTag获取这个系统控件身上的自定义属性,执行相应的动画如水平移动,垂直移动等。我们先看一下今天要展示的实例运行效果:
源码下载地址:https://download.csdn.net/download/gaoxiaoweiandy/11142066。
这个效果是一个ViewPager滑动,然后每一个页面中的元素运动速度不一样,有的元素移动的速度明显比其它元素快,这样就形成了视差效果。首先我们来看一下这个框架的用法:
-
package com.example.animateframe2;
-
-
-
import android.os.Bundle;
-
import android.support.v4.app.FragmentActivity;
-
import android.widget.ImageView;
-
-
public class SplashActivity extends FragmentActivity {
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.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
-
});
-
-
//设置动画
-
ImageView iv_man = (ImageView) findViewById(R.id.iv_man);
-
iv_man.setBackgroundResource(R.drawable.man_run);
-
container.setIv_man(iv_man);
-
-
}
-
-
-
}
为ParallaxContainer设置了几个布局,将来这几个布局可以滑动,然后还有自定义属性动画。就这么简单。下面那个iv_man就是一个简单的播放帧动画(小人走路)。
我们从最初的框架使用方法倒推这个动画框架的内部实现:这个ParallaxContainer,既然可以滑动,里面应该包含了ViewPager, 滑动时执行动画,说明我们为ViewPager设置了页面滑动监听“OnPageChangeListener”,在这个监听器里根据滑动的距离来执行动画。 滑动的每一个页面是一个Fragment。 我们来看看ParallaxContainer的代码是不是包含这些,代码如下:
-
package com.example.animateframe2;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
-
import com.nineoldandroids.view.ViewHelper;
-
-
import android.content.Context;
-
import android.graphics.drawable.AnimationDrawable;
-
import android.os.Bundle;
-
import android.support.v4.view.ViewPager;
-
import android.support.v4.view.ViewPager.OnPageChangeListener;
-
import android.util.AttributeSet;
-
import android.util.Log;
-
import android.view.View;
-
import android.widget.FrameLayout;
-
import android.widget.ImageView;
-
-
/**
-
* 引导页的最外层布局
-
*/
-
public class ParallaxContainer extends FrameLayout implements OnPageChangeListener {
-
-
private List<ParallaxFragment> fragments;
-
private ParallaxPagerAdapter adapter;
-
private float containerWidth;
-
private ImageView iv_man;
-
-
public ParallaxContainer(Context context, AttributeSet attrs) {
-
super(context, attrs);
-
}
-
-
/**
-
* 指定引导页的所有页面布局文件
-
* @param childIds
-
*/
-
public void setUp(int... childIds){
-
//根据布局文件数组,初始化所有的fragment
-
fragments = new ArrayList<ParallaxFragment>();
-
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);
-
}
-
-
-
//实例化适配器
-
SplashActivity activity = (SplashActivity)getContext();
-
adapter = new ParallaxPagerAdapter(activity.getSupportFragmentManager(), fragments);
-
-
//实例化ViewPager
-
ViewPager vp = new ViewPager(getContext());
-
vp.setId(R.id.parallax_pager);
-
vp.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
-
-
//绑定
-
vp.setAdapter(adapter);
-
addView(vp,0);
-
-
vp.setOnPageChangeListener(this);
-
-
}
-
-
-
@Override
-
public void onPageScrolled(int position, float positionOffset,
-
int positionOffsetPixels) {
-
this.containerWidth = getWidth();
-
//在翻页的过程中,不断根据视图的标签中对应的动画参数,改变视图的位置或者透明度
-
//获取到进入的页面
-
ParallaxFragment inFragment = null;
-
try {
-
inFragment = fragments.get(position - 1);
-
} catch (Exception e) {}
-
-
//获取到退出的页面
-
ParallaxFragment outFragment = null;
-
try {
-
outFragment = fragments.get(position);
-
} catch (Exception e) {}
-
-
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;
-
}
-
//translationY改变view的偏移位置,translationY=100,代表view在其原始位置向下移动100
-
//仔细观察进入的fragment中view从远处过来,不断向下移动,最终停在原始位置
-
ViewHelper.setTranslationY(view, (containerWidth - positionOffsetPixels) * tag.yIn);
-
ViewHelper.setTranslationX(view, (containerWidth - positionOffsetPixels) * tag.xIn);
-
}
-
}
-
}
-
-
if(outFragment != null){
-
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;
-
}
-
//仔细观察退出的fragment中view从原始位置开始向上移动,translationY应为负数
-
ViewHelper.setTranslationY(view, 0 - positionOffsetPixels * tag.yOut);
-
ViewHelper.setTranslationX(view, 0 - positionOffsetPixels * tag.xOut);
-
}
-
}
-
}
-
-
}
-
-
@Override
-
public void onPageSelected(int position) {
-
if (position == adapter.getCount() - 1) {
-
iv_man.setVisibility(INVISIBLE);
-
}else{
-
iv_man.setVisibility(VISIBLE);
-
}
-
}
-
-
@Override
-
public void onPageScrollStateChanged(int state) {
-
AnimationDrawable animation = (AnimationDrawable) iv_man.getBackground();
-
switch (state) {
-
case ViewPager.SCROLL_STATE_DRAGGING:
-
animation.start();
-
break;
-
-
case ViewPager.SCROLL_STATE_IDLE:
-
animation.stop();
-
break;
-
-
default:
-
break;
-
}
-
}
-
-
public void setIv_man(ImageView iv_man) {
-
this.iv_man = iv_man;
-
}
-
-
}
正如我们所分析的,setUp函数里确实为ViewPager设置了一个以frament数组为页面列表的adapter.还有一个
onPageScrolled函数来监听滑动的距离,根据页面滑出,进入的比例执行动画。我们来看一下onPageScrolled的各个参数:
onPageScrolled(int position, float positionOffset,int positionOffsetPixels)
position:当前正在滑动的页面,
positionOffset: 滑动的比例,例如0.5表示当前页面已滑动了一半。
positionOffsetPixels: 滑动的比例对应的屏幕像素值,如屏幕宽度的一半像素。
然后这个onPageScrolled里面就是执行动画的代码,首先使用view.getTag来获取与view元素关联的自定义属性对象(里面包含了这个view设置的所有自定义属性及值),然后调用setTranslationY这类的平移函数来实现VIEW元素平移动画。其中inFragment是进入的页面,outFragment是要滑出的页面。 这里面的滑动算法我们无需关心,随自己定。我们主要关心的是这个view.getTag获取系统控件(如Imageview)的自定义属性,这个TAG是什么时候set进去的,以及这个tag里的自定义属性值是怎么获取到的。有于每一个页面中的布局是在Fragment里加载的,因此我们来看一下Fragment加载布局的代码。
ParallaxFragment.java代码:
-
package com.example.animateframe2;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
-
import android.os.Bundle;
-
import android.support.v4.app.Fragment;
-
import android.util.Log;
-
import android.view.LayoutInflater;
-
import android.view.View;
-
import android.view.ViewGroup;
-
-
public class ParallaxFragment extends Fragment {
-
-
//此Fragment上所有的需要实现视差动画的视图
-
private List<View> parallaxViews = new ArrayList<View>();
-
-
-
@Override
-
public View onCreateView(LayoutInflater original, ViewGroup container,
-
Bundle savedInstanceState) {
-
Bundle args = getArguments();
-
int layoutId = args.getInt("layoutId");
-
int index = args.getInt("index");
-
Log.d("jason", "fragment:"+index);
-
//1.布局加载器将布局加载进来了
-
//2.解析创建布局上所有的视图
-
//3.自己搞定创建视图的过程
-
//4.获取视图相关的自定义属性的值
-
ParallaxLayoutInflater inflater = new ParallaxLayoutInflater(original, getActivity(),this);
-
-
return inflater.inflate(layoutId, null);
-
}
-
-
-
public List<View> getParallaxViews() {
-
return parallaxViews;
-
}
-
-
}
我们看一下关键的两行代码:
ParallaxLayoutInflater inflater = new ParallaxLayoutInflater(original, getActivity(),this);
return inflater.inflate(layoutId, null);
我们自定义了一个LayoutInflater: ParallaxLayoutInflater,用自定义的ParallaxLayoutInflater去解析XML布局layoutId.
我们自定义LayoutInflater的目的,无非是想在解析XML里的各个VIEW时做些手脚,做什么手脚呢? 解析每一个XML的view元素,并获取它身上的多个自定义属性,封装在一个tag对象里,然后调用view.setTag将view与自定义属性关联起来。
通过阅读系统LayoutInflater源码,系统API是在LayoutInflater的Factory2对象里的onCreateView函数里来解析xml元素的。因此我们想办法自定义LayoutInflater,并重写Factory2的onCreateView函数。我们先来看一下ParallaxLayoutInflater的代码,
自定义LayoutInflater:ParallaxLayoutInflater.java:
-
package com.example.animateframe2;
-
-
-
import android.content.Context;
-
import android.content.res.TypedArray;
-
import android.util.AttributeSet;
-
import android.util.Log;
-
import android.view.LayoutInflater;
-
import android.view.View;
-
-
public class ParallaxLayoutInflater extends LayoutInflater {
-
-
private ParallaxFragment fragment;
-
-
protected ParallaxLayoutInflater(LayoutInflater original, Context newContext,ParallaxFragment fragment) {
-
super(original, newContext);
-
this.fragment = fragment;
-
-
//重新设置布局加载器的工厂
-
//工厂:创建布局文件中所有的视图
-
setFactory2(new ParallaxFactory(this));
-
-
}
-
-
@Override
-
public LayoutInflater cloneInContext(Context newContext) {
-
return this;
-
//return new ParallaxLayoutInflater(this,newContext,fragment);
-
}
-
-
class ParallaxFactory implements Factory2{
-
-
private LayoutInflater inflater;
-
private final String[] sClassPrefix = {
-
"android.widget.",
-
"android.view."
-
};
-
public ParallaxFactory(LayoutInflater inflater) {
-
this.inflater = inflater;
-
}
-
-
//自定义,视图创建的过程
-
@Override
-
public View onCreateView(String name, Context context,
-
AttributeSet attrs) {
-
View view = null;
-
if (view == null) {
-
view = createViewOrFailQuietly(name,context,attrs);
-
}
-
-
//实例化完成
-
if (view != null) {
-
//获取自定义属性,通过标签关联到视图上
-
setViewTag(view,context,attrs);
-
fragment.getParallaxViews().add(view);
-
Log.d("ricky", "view:"+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 a = context.obtainStyledAttributes(attrs, attrIds);
-
if (a != null && a.length() > 0) {
-
//获取自定义属性的值
-
ParallaxViewTag tag = new ParallaxViewTag();
-
tag.alphaIn = a.getFloat(0, 0f);
-
tag.alphaOut = a.getFloat(1, 0f);
-
tag.xIn = a.getFloat(2, 0f);
-
tag.xOut = a.getFloat(3, 0f);
-
tag.yIn = a.getFloat(4, 0f);
-
tag.yOut = a.getFloat(5, 0f);
-
-
//index
-
view.setTag(R.id.parallax_view_tag,tag);
-
}
-
-
a.recycle();
-
-
}
-
-
private View createViewOrFailQuietly(String name, String prefix,Context context,
-
AttributeSet attrs) {
-
try {
-
//通过系统的inflater创建视图,读取系统的属性
-
return inflater.createView(name, prefix, attrs);
-
} catch (Exception e) {
-
return null;
-
}
-
}
-
-
private View createViewOrFailQuietly(String name, Context context,
-
AttributeSet attrs) {
-
//1.自定义控件标签名称带点,所以创建时不需要前缀
-
if (name.contains(".")) {
-
createViewOrFailQuietly(name, null, context, attrs);
-
}
-
//2.系统视图需要加上前缀
-
for (String prefix : sClassPrefix) {
-
View view = createViewOrFailQuietly(name, prefix, context, attrs);
-
if (view != null) {
-
return view;
-
}
-
}
-
-
return null;
-
}
-
-
@Override
-
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-
View view = null;
-
if (view == null) {
-
view = createViewOrFailQuietly(name,context,attrs);
-
}
-
-
//实例化完成
-
if (view != null) {
-
//获取自定义属性,通过标签关联到视图上
-
setViewTag(view,context,attrs);
-
fragment.getParallaxViews().add(view);
-
Log.d("ricky", "view:"+view);
-
}
-
-
return view;
-
}
-
}
-
-
}
代码分析:
1. 首先继承LayoutInflater
2. 然后自定义一个Factory2: ParallaxFactory , 并重写onCreateview方法,调用
view = createViewOrFailQuietly(name,context,attrs);获取系统为我们解析的XML元素view实例。
-
private View createViewOrFailQuietly(String name, Context context,
-
AttributeSet attrs) {
-
//1.自定义控件标签名称带点,所以创建时不需要前缀
-
if (name.contains(".")) {
-
createViewOrFailQuietly(name, null, context, attrs);
-
}
-
//2.系统视图需要加上前缀
-
for (String prefix : sClassPrefix) {
-
View view = createViewOrFailQuietly(name, prefix, context, attrs);
-
if (view != null) {
-
return view;
-
}
-
}
-
-
return null;
-
}
-
-
-
private View createViewOrFailQuietly(String name, String prefix,Context context,
-
AttributeSet attrs) {
-
try {
-
//通过系统的inflater创建视图,读取系统的属性
-
return inflater.createView(name, prefix, attrs);
-
} catch (Exception e) {
-
return null;
-
}
-
}
为什么是系统帮我们解析的,因为在这个函数里面我们最终调用的是 return inflater.createView(name, prefix, attrs);
那为什么我们不直接调用系统的inflater.createView(name, prefix, attrs)函数来获取xml中的view实例呢。因为在这个createViewOrFailQuietly函数里我们要区分自定义控件与系统控件,如果是自定义控件的话,整个name就是一个完成的类路径,所以prefix为null.如何是系统控件,如imageview,那么prefix得传递系统控件前缀
-
"android.widget.",
-
"android.view."
OK,这一步我们获得了每一个XML中的view。
3. 获取自定义属性,封装到TAG
if (view != null) {
//获取自定义属性,通过标签关联到视图上
setViewTag(view,context,attrs);
fragment.getParallaxViews().add(view);
Log.d("ricky", "view:"+view);
}
这里setViewTag函数完成了这个功能:
-
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 a = context.obtainStyledAttributes(attrs, attrIds);
-
if (a != null && a.length() > 0) {
-
//获取自定义属性的值
-
ParallaxViewTag tag = new ParallaxViewTag();
-
tag.alphaIn = a.getFloat(0, 0f);
-
tag.alphaOut = a.getFloat(1, 0f);
-
tag.xIn = a.getFloat(2, 0f);
-
tag.xOut = a.getFloat(3, 0f);
-
tag.yIn = a.getFloat(4, 0f);
-
tag.yOut = a.getFloat(5, 0f);
-
-
//index
-
view.setTag(R.id.parallax_view_tag,tag);
-
}
-
-
a.recycle();
-
-
}
fragment.getParallaxViews().add(view);这行代码是将每一页fragment中的view元素放到一个数组里。以方便我们在前面的viewPager : onPageScrolled函数里遍历每一个view并获取这个view上的自定义属性。
Ok,至此我们从最初的框架使用方法倒推出了这个动画框架的内部实现。
源码下载地址:https://download.csdn.net/download/gaoxiaoweiandy/11142066。
文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/89493652
- 点赞
- 收藏
- 关注作者
评论(0)