在开发中,为了实现某些固定可配置的动画,即在布局中实现进行配置(或者在代码中配置)就可以完成的某些动画框架。
其实,说白了也就是自定义特殊控件,在控件中处理一些常用可规范的动画效果,比如:随着滑动而伴随的加速减速显现、缩放、透明度等效果。
做一个自己封装好的效果需要考虑:便于使用和重用;便于配置;
实现的思路:
(1)自定义view
(2)设置可选择的配置参数
(3)处理动画
实现的方式:
1.偷天换日,给每一个需要配置自定义属性的子控件外面包裹一层自定义容器。
(1)DiscrollView:重写ScrollView,处理滑动监听,并将动画需要的数据通过接口的方式传递给view
public class DiscrollView extends ScrollView {
private DiscrollViewContent mContent;
public DiscrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
View content = getChildAt(0);
mContent = (DiscrollViewContent)content;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
View first = mContent.getChildAt(0);
first.getLayoutParams().height = getHeight();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
super.onScrollChanged(l, t, oldl, oldt);
int scrollViewHeight = getHeight();
//监听滑动----接口---->控制DiscrollViewContent的属性
for(int i=0;i<mContent.getChildCount();i++){//遍历MyLinearLayout里面所有子控件(MyViewGroup)
View child = mContent.getChildAt(i);
if(!(child instanceof DiscrollvableInterface)){
continue;
}
//ratio:0~1
DiscrollvableInterface discrollvableInterface = (DiscrollvableInterface) child;
//1.child离scrollview顶部的高度 a
int discrollvableTop = child.getTop();
int discrollvableHeight = child.getHeight();
//2.得到scrollview滑出去的高度 b 就是int t,
//3.得到child离屏幕顶部的高度 c
int discrollvableAbsoluteTop = discrollvableTop - t;
//什么时候执行动画?当child滑进屏幕的时候
if(discrollvableAbsoluteTop<=scrollViewHeight){
int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
//确保ratio是在0~1,超过了1 也设置为1
discrollvableInterface.onDiscrollve(clamp(visibleGap/(float)discrollvableHeight, 1f,0f));
}else{//否则,就恢复到原来的位置
discrollvableInterface.onResetDiscrollve();
}
}
}
public static float clamp(float value, float max, float min){
return Math.max(Math.min(value, max), min);
}
}
(2)DiscrollViewContent,重写LinearLayout,主要是将子view进行一层包装,这里使用FrameLayout。核心就在此,只要这个FrameLayout执行动画,那么里面被包裹的view也就依附于包裹容器呈现出动画效果,偷梁换柱的实现这个效果。
同时,它将所有配置数据封装起来,设置给了包裹容器,方便包包裹容器做处理。
public class DiscrollViewContent extends LinearLayout {
public DiscrollViewContent(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
// 得到xml里面穿过来的参数
return new MyLayoutParams(getContext(),attrs);
}
@Override
public void addView(View child, int index,
android.view.ViewGroup.LayoutParams params) {
//从child里面拿到我自定义的属性,传到discrollvableView里面
MyLayoutParams p = (MyLayoutParams) params;
if(!isDiscrollvable(p)){//判断该view是否穿了自定义属性值,不是就不需要执行动画,不包一层FrameLayout
super.addView(child, index, params);
}else{
//在addView里面插一脚,往child外面包裹一层FrameLayout
DiscrollvableView discrollvableView = new DiscrollvableView(getContext());
discrollvableView.setmDiscrollveAlpha(p.mDiscrollveAlpha);
discrollvableView.setmDisCrollveTranslation(p.mDisCrollveTranslation);
discrollvableView.setmDiscrollveScaleX(p.mDiscrollveScaleX);
discrollvableView.setmDiscrollveScaleY(p.mDiscrollveScaleY);
discrollvableView.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
discrollvableView.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);
discrollvableView.addView(child);
super.addView(discrollvableView, index, params);
}
}
private boolean isDiscrollvable(MyLayoutParams p) {
// TODO Auto-generated method stub
return p.mDiscrollveAlpha||
p.mDiscrollveScaleX||
p.mDiscrollveScaleY||
p.mDisCrollveTranslation!=-1||
(p.mDiscrollveFromBgColor!=-1&&
p.mDiscrollveToBgColor!=-1);
}
public static class MyLayoutParams extends LinearLayout.LayoutParams{
public int mDiscrollveFromBgColor;//背景颜色变化开始值
public int mDiscrollveToBgColor;//背景颜色变化结束值
public boolean mDiscrollveAlpha;//是否需要透明度动画
public int mDisCrollveTranslation;//平移值
public boolean mDiscrollveScaleX;//是否需要x轴方向缩放
public boolean mDiscrollveScaleY;//是否需要y轴方向缩放
public MyLayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
// 从child里面拿到我自定义的属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);
mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
mDisCrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
a.recycle();
}
}
}
(3)DiscrollvableView,继承了FrameLayout,它是包裹容器,用来处理动画效果。动画效果的执行用接口实现。
public class DiscrollvableView extends FrameLayout implements DiscrollvableInterface{
/**
* <attr name="discrollve_translation">
<flag name="fromTop" value="0x01" />
<flag name="fromBottom" value="0x02" />
<flag name="fromLeft" value="0x04" />
<flag name="fromRight" value="0x08" />
</attr>
0000000001
0000000010
0000000100
0000001000
0000000101
*/
private static final int TRANSLATION_FROM_TOP = 0x01;
private static final int TRANSLATION_FROM_BOTTOM = 0x02;
private static final int TRANSLATION_FROM_LEFT = 0x04;
private static final int TRANSLATION_FROM_RIGHT = 0x08;
//颜色估值器
private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
/**
* 自定义属性的一些接收的变量
*/
private int mDiscrollveFromBgColor;//背景颜色变化开始值
private int mDiscrollveToBgColor;//背景颜色变化结束值
private boolean mDiscrollveAlpha;//是否需要透明度动画
private int mDisCrollveTranslation;//平移值
private boolean mDiscrollveScaleX;//是否需要x轴方向缩放
private boolean mDiscrollveScaleY;//是否需要y轴方向缩放
private int mHeight;//本view的高度
private int mWidth;//宽度
public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) {
this.mDiscrollveFromBgColor = mDiscrollveFromBgColor;
}
public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) {
this.mDiscrollveToBgColor = mDiscrollveToBgColor;
}
public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) {
this.mDiscrollveAlpha = mDiscrollveAlpha;
}
public void setmDisCrollveTranslation(int mDisCrollveTranslation) {
this.mDisCrollveTranslation = mDisCrollveTranslation;
}
public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) {
this.mDiscrollveScaleX = mDiscrollveScaleX;
}
public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) {
this.mDiscrollveScaleY = mDiscrollveScaleY;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
onResetDiscrollve();
}
public DiscrollvableView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public DiscrollvableView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
public void onDiscrollve(float ratio) {
// ratio:0~1
//控制自身的动画属性
if(mDiscrollveAlpha){
setAlpha(ratio);
}
if(mDiscrollveScaleX){
setScaleX(ratio);
}
if(mDiscrollveScaleY){
setScaleY(ratio);
}
// int mDisCrollveTranslation 有很多种枚举的情况
//判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
//fromBottom
if(isDiscrollTranslationFrom(TRANSLATION_FROM_BOTTOM)){
setTranslationY(mHeight*(1-ratio));//mHeight-->0(代表原来的位置)
}
if(isDiscrollTranslationFrom(TRANSLATION_FROM_TOP)){
setTranslationY(-mHeight*(1-ratio));//-mHeight-->0(代表原来的位置)
}
if(isDiscrollTranslationFrom(TRANSLATION_FROM_LEFT)){
setTranslationX(-mWidth*(1-ratio));//-width-->0(代表原来的位置)
}
if(isDiscrollTranslationFrom(TRANSLATION_FROM_RIGHT)){
setTranslationX(mWidth*(1-ratio));//width-->0(代表原来的位置)
}
//颜色渐变动画
if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){
//ratio=0.5 color=中间颜色
setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
}
}
private boolean isDiscrollTranslationFrom(int translationMask) {
if(mDisCrollveTranslation==-1){
return false;
}
//fromLeft|fromBottom & fromBottom = fromBottom
return (mDisCrollveTranslation & translationMask)==translationMask;
}
@Override
public void onResetDiscrollve() {
//控制自身的动画属性
if(mDiscrollveAlpha){
setAlpha(0);
}
if(mDiscrollveScaleX){
setScaleX(0);
}
if(mDiscrollveScaleY){
setScaleY(0);
}
// int mDisCrollveTranslation 有很多种枚举的情况
//判断到底是哪一种值:fromTop,fromBottom,fromLeft,fromRight
//fromBottom
if(isDiscrollTranslationFrom(TRANSLATION_FROM_BOTTOM)){
setTranslationY(mHeight);//mHeight-->0(代表原来的位置)
}
if(isDiscrollTranslationFrom(TRANSLATION_FROM_TOP)){
setTranslationY(-mHeight);//-mHeight-->0(代表原来的位置)
}
if(isDiscrollTranslationFrom(TRANSLATION_FROM_LEFT)){
setTranslationX(-mWidth);//-width-->0(代表原来的位置)
}
if(isDiscrollTranslationFrom(TRANSLATION_FROM_RIGHT)){
setTranslationX(mWidth);//width-->0(代表原来的位置)
}
}
}
(4)DiscrollvableInterface,动画执行的回调接口
public interface DiscrollvableInterface {
/**
* 当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
* @param ratio
*/
public void onDiscrollve(float ratio);
/**
* 重置view的属性----恢复view的原来属性
*/
public void onResetDiscrollve();
}
(5)Activity的布局
<com.seasons.live.discrollview.DiscrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:discrollve="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.seasons.live.discrollview.DiscrollViewContent
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="600dp"
android:background="@android:color/white"
android:textColor="@android:color/black"
android:textSize="25sp"
android:padding="25dp"
tools:visibility="gone"
android:gravity="center"
android:fontFamily="serif"
android:text="冯绍峰对着倪妮发誓说:‘’如果有一天我离开了你,我就把名字倒着念‘’。倪妮说:‘我也是’’!——尼玛,看着我也是醉了!" />
<View
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#007788"
discrollve:discrollve_alpha="true"
/>
<ImageView
android:layout_width="200dp"
android:layout_height="120dp"
discrollve:discrollve_alpha="true"
discrollve:discrollve_translation="fromLeft|fromBottom"
android:src="@drawable/baggage" />
<View
android:layout_width="match_parent"
android:layout_height="200dp"
discrollve:discrollve_fromBgColor="#ffff00"
discrollve:discrollve_toBgColor="#88EE66" />
<ImageView
android:layout_width="220dp"
android:layout_height="110dp"
android:layout_gravity="right"
android:src="@drawable/camera"
discrollve:discrollve_translation="fromRight" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:fontFamily="serif"
android:gravity="center"
android:text="眼见范冰冰与李晨在一起了,孩子会取名李冰冰;李冰冰唯有嫁给范伟,生个孩子叫范冰冰,方能扳回一城。"
android:textSize="23sp"
discrollve:discrollve_alpha="true"
discrollve:discrollve_translation="fromBottom" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_gravity="center"
android:src="@drawable/sweet"
discrollve:discrollve_scaleX="true"
discrollve:discrollve_scaleY="true" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_gravity="center"
android:src="@drawable/camera"
discrollve:discrollve_translation="fromLeft|fromBottom"
/>
</com.seasons.live.discrollview.DiscrollViewContent>
</com.seasons.live.discrollview.DiscrollView>
2.偷天换日,可以自定义LayoutInflater,自己来控制view的加载。
(1)核心:在加载view的时候使用重写新的ParallaxLayoutInflater。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
Bundle bundle = getArguments();
int layoutId = bundle.getInt("layoutId");
// View view = inflater.inflate(layoutId, container);
ParallaxLayoutInflater layoutInflater = new ParallaxLayoutInflater(inflater, getActivity(),this);
return layoutInflater.inflate(layoutId, null);
}
其实就是在渲染view这个步骤的前面进行了一层处理,获取子view中所有自定义属性,并将这些属性封装成一个tag数据,并给view添加一个tag,方便在动画的时候根据view的属性来做动画处理。至于View的加载过程,可查看LayoutInflater源码。
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));
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
// TODO Auto-generated method stub
return new ParallaxLayoutInflater(this, newContext,fragment);
}
//自定义工厂类,视图创建的工厂类
class ParallaxFactory implements Factory{
private LayoutInflater inflater;
public ParallaxFactory(LayoutInflater inflater) {
this.inflater = inflater;
}
@Override
public View onCreateView(String name, Context context,
AttributeSet attrs) {
//1.实例化里面的view
View view = null;
if(view==null){
view = createView(name,context,attrs);
}
Log.i("ricky", "view:"+view);
if(view!=null){
//获取自定义属性,并将自定义标签值绑定到view上面
getCustomAttrs(context,attrs,view);
fragment.getViews().add(view);
}
return view;
}
private void getCustomAttrs(Context context, AttributeSet attrs, View 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,
};
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);
view.setTag(R.id.parallax_view_tag, tag);
// view.setTag(obj);
}
a.recycle();
}
private final String[] prefixs = {
"android.widget.",
"android.view."
};
private View createView(String name, String prefix,Context context, AttributeSet attrs) {
try {
return inflater.createView(name, prefix, attrs);
} catch (Exception e) {
return null;
}
}
private View createView(String name, Context context, AttributeSet attrs) {
//通过系统的inflater类创建视图
if(name.contains(".")){//自定义控件,已经是全类名了
return createView(name, null,context, attrs);
}else{
// android.widget.ImageView "android.widget."+name
// android.view.SurfaceView
for(String prefix:prefixs){
View view = createView(name, prefix, context, attrs);
if(view!=null){
return view;
}
}
}
return null;
}
}
}
封装的tag:
public class ParallaxViewTag {
protected int index;
protected float xIn;
protected float xOut;
protected float yIn;
protected float yOut;
protected float alphaIn;
protected float alphaOut;
@Override
public String toString() {
return "ParallaxViewTag [index=" + index + ", xIn=" + xIn + ", xOut="
+ xOut + ", yIn=" + yIn + ", yOut=" + yOut + ", alphaIn="
+ alphaIn + ", alphaOut=" + alphaOut + "]";
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="parallax_pager" type="id"/>
<item name="parallax_view_tag" type="id"/>
</resources>
viewpage滑动的时候处理:
public class ParallaxContainer extends FrameLayout implements OnPageChangeListener {
private List<ParallaxFragment> fragments;
private float containerWidth;
public ParallaxContainer(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public void setUp(int... ids ){
fragments = new ArrayList<ParallaxFragment>();
for (int i = 0; i <ids.length ; i++) {
ParallaxFragment f = new ParallaxFragment();
Bundle args = new Bundle();
args.putInt("layoutId", ids[i]);
f.setArguments(args );
fragments.add(f);
}
//1.viewpager
ViewPager vp = new ViewPager(getContext());
vp.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
vp.setId(R.id.parallax_pager);
ParallaxAdapter adapter = new ParallaxAdapter(((FragmentActivity)getContext()).getSupportFragmentManager(), fragments);
vp.setAdapter(adapter);
addView(vp, 0);
vp.addOnPageChangeListener(this);
}
@Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
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){
List<View> views = inFragment.getViews();
for (int i = 0; i < views.size(); i++) {
View view = views.get(i);
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
Log.i("ricky", "tag_in:"+tag);
if(tag==null) continue;
//进来动画
view.setTranslationX((containerWidth-positionOffsetPixels)*tag.xIn);
view.setTranslationY((containerWidth-positionOffsetPixels)*tag.yIn);
}
}
if(outFragment!=null){
List<View> views = outFragment.getViews();
for (int i = 0; i < views.size(); i++) {
View view = views.get(i);
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
Log.i("ricky", "tag_out:"+tag);
if(tag==null) continue;
//进来动画
view.setTranslationX((-positionOffsetPixels)*tag.xOut);
view.setTranslationY((-positionOffsetPixels)*tag.yOut);
}
}
}
@Override
public void onPageSelected(int arg0) {
// TODO Auto-generated method stub
}
}
其中一个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/com.ricky.parallax"
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.8" />
<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>
归根到底,无论是自定义view还是自定义动画框架,所做的事情都是在重新封装view,在view中处理各种事件和滑动。
其核心就是:
(1)如何获取布局中设置的属性值
(2)渲染view和动画处理
(3)事件监听处理
所以,必须掌握View的渲染流程和事件传递机制,这也是View的核心所在。