an animation framework for optical parallax scroll effects
一个用来打造上述引导界面动画效果的Scroll框架, 集成进https://github.com/Jerey-Jobs/KeepGank中,作为首次启动的欢迎界面。
目前支持:
- 透明度动画与平移动画(四种方向),支持混合调用
- 背景色渐变设置
- SherlockLinearLayout与SherlockRelativeLayout提供动画界面的线性布局与相对布局支持
- SherlockAnimationCallBack提供自定义扩展
如何使用
project's build.gradle (工程下的 build.gradle)
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
module's build.gradle (模块的build.gradle)
dependencies {
compile 'com.github.Jerey-Jobs:ScrollAnimationSherlock:1.0'
}
顶层布局:cn.jerey.animationlib.SherlockScrollView,内嵌一个SherlockLinearLayout
<cn.jerey.animationlib.SherlockScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<cn.jerey.animationlib.SherlockLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<include layout="@layout/splash_layout"></include>
</cn.jerey.animationlib.SherlockLinearLayout>
</cn.jerey.animationlib.SherlockScrollView>
SherlockLinearLayout
的第一个子View会被默认设置为全屏,因此
<include layout="@layout/splash_layout"></include>
在splash_layout
中完成第一个界面的搭建
接下来就是使用SherlockLinearLayout
与SherlockRelativeLayout
进行其他界面搭建。demo中使用的是SherlockRelativeLayout
其中放置了四个子View,并设置了相应动画。
<cn.jerey.animationlib.SherlockRelativeLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<ImageView
android:id="@+id/moon"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="30dp"
android:background="@drawable/moon"
app:animation_alpha="true"
app:animation_translation="left"/>
<ImageView
android:id="@+id/astronaut"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="120dp"
android:layout_marginTop="28dp"
android:background="@drawable/astronaut"
app:animation_alpha="true"
app:animation_translation="right|bottom"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="200dp"
android:layout_height="180dp"
android:layout_alignParentRight="true"
android:layout_marginTop="100dp"
android:background="@drawable/planet_earth_1"
app:animation_alpha="true"
app:animation_translation="right|bottom"/>
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="30dp"
android:background="@drawable/rocket_1"
app:animation_alpha="true"/>
</cn.jerey.animationlib.SherlockRelativeLayout>
总的原理一句话:赋予每个需要进行动画变换的View动画属性,并根据位置改变属性。
我们需要做的事情有
- 如何确定某个View需要进行动画变换
- 确定后如何赋予动画属性
- 如何分发动画变换事件,让子View进行变换
ViewGroup的addView方法,该方法是添加子View的时候调用的。
public void addView(View child, int index, ViewGroup.LayoutParams params)
我们需要在这边进行子View的判断,如何判断呢,我们可以参照support包的设计,添加app属性,
我们去定义几个属性
<attr name="animation_alpha" format="boolean" />//是否支持透明度动画;
<attr name="animation_scaleX" format="boolean" />//是否支持X轴缩放动画;
<attr name="animation_scaleY" format="boolean" />//是否支持Y轴缩放动画;
<attr name="bgColorStart" format="color" />//背景渐变颜色的开始颜色值;
<attr name="bgColorEnd" format="color" />//背景渐变颜色的结束颜色值,与bgColorStart成对出现;
<attr name="animation_translation">//移动动画,是一个枚举类型,支持上下左右四种值。
<flag name="left" value="0x01" />
<flag name="top" value="0x02" />
<flag name="right" value="0x04" />
<flag name="bottom" value="0x08" />
</attr>
在addView时候,通过layoutParams参数来判断,那么这里的LayoutParams是我们自定义的,继承于系统的LayoutParams, 不过在其构造方法时追加参数解析。
/**
* 不能将此LayoutParams抽象出来, 其继承的是自己内部类的Params
*/
public class RelativeLayoutParams extends LayoutParams {
//是否支持透明度;
public boolean mAlphaSupport;
//是否支持X Y轴缩放;
public boolean mScaleXSupport;
public boolean mScaleYSupport;
//颜色变化的起始值;
public int mBgColorStart;
public int mBgColorEnd;
//移动值;
public int mTranslationValue;
public RelativeLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyFrameLayout);
mAlphaSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_animation_alpha, false);
mBgColorStart = typedArray.getColor(R.styleable.MyFrameLayout_bgColorStart, -1);
mBgColorEnd = typedArray.getColor(R.styleable.MyFrameLayout_bgColorEnd, -1);
mScaleXSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_animation_scaleX, false);
mScaleYSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_animation_scaleY, false);
mTranslationValue = typedArray.getInt(R.styleable.MyFrameLayout_animation_translation, -1);
typedArray.recycle();
}
/**
* 判断当前params是否包含自定义属性;
*
* @return
*/
public boolean isHaveMyProperty() {
if (mAlphaSupport || mScaleXSupport || mScaleYSupport || (mBgColorStart != -1 && mBgColorEnd != -1) || mTranslationValue != -1) {
return true;
}
return false;
}
}
这样我们在addView时能够拿到这个params,并且里面已经解析了是否支持动画了。
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
RelativeLayoutParams myLayoutParams = (RelativeLayoutParams) params;
if (myLayoutParams.isHaveMyProperty())
我们的View是不大可能自己动的,而且我们也没法去改view的代码。这些view都是系统的view, 这样我们只能说让view有一个父类,去操作它了。或者说。给View“伪增加”一个方法,使其接收到我们的移动事件后,能够进行动画变换。
如何增加呢?
我们在解析view的属性时,即addView
时,在其外面包裹一层父View,我称之为 Frame。 使用FrameView去包裹它,
当然需要注意的是,为了让view能够直接完整的进行动画显示。我们需要设置各个父类的ClipChildren
属性为false。
因此封装了SherlockFrame
,其继承于FrameLayout,并实现我们的位移callback接口的,注意只有实现了我们的位移回调接口的。我们分发事件时才会分发。
同样,这个接口提供了自定义扩展,可以自己编写实现这个接口的自定义view,同样会接收到位移分发。
public class SherlockFrame extends FrameLayout implements SherlockAnimationCallBack{
//从哪个方向开始动画;
private static final int TRANSLATION_LEFT = 0x01;
private static final int TRANSLATION_TOP = 0x02;
private static final int TRANSLATION_RIGHT = 0x04;
private static final int TRANSLATION_BOTTOM = 0x08;
//是否支持透明度;
private boolean mAlphaSupport;
//颜色变化的起始值;
private int mBgColorStart;
private int mBgColorEnd;
//是否支持X Y轴缩放;
private boolean mScaleXSupport;
private boolean mScaleYSupport;
//移动值;
private int mTranslationValue;
//当前View宽高;
private int mHeight, mWidth;
}
SherlockFrame
的这些属性,会在addview的时候进行赋值。
SherlockAnimationCallBack
回调excuteanimation
方法时,SherlockFrame
就根据自身的属性情况进行动画变换。
作为scrollView,滚动事件的分发肯定是在onScrollChanged
了。在这里面进行滚动事件分发
parseViewGroup方法有点讲究了,这是一个递归遍历子view,看其是否实现了SherlockAnimationCallBack
接口,若没有则去判断是否是ViewGroup,若是的话则继续递归遍历其子view。
若是实现了SherlockAnimationCallBack
接口的view。我们要根据其距离顶部的高度来计算动画应该执行百分之多少。我们可以通过view的getTop方法,这个方法是得到view距离其父view的顶部的距离。
因此我们还要进行递归传递。
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
parseViewGroup(mLinearLayout, l, t, oldl, oldt, true, 0);
}
/**
* @param linearLayout
* @param l
* @param t
* @param oldl
* @param oldt
* @param isRootLinearLayout 是否是顶层布局
* @param getTop 距离顶部高度
*/
private void parseViewGroup(ViewGroup linearLayout,
int l, int t, int oldl, int oldt,
boolean isRootLinearLayout, int getTop) {
int scrollViewHeight = getHeight();
Log.w(TAG, "linearLayout.getChildCount()" + linearLayout.getChildCount());
for (int i = 0; i < linearLayout.getChildCount(); i++) {
//如果子控件不是MyFrameLayout则循环下一个子控件;
View child = linearLayout.getChildAt(i);
// 若不是动画控件,则进入判断是否是ViewGroup,是的话递归其子view.不是的话则判断下一个
if (!(child instanceof SherlockAnimationCallBack)) {
if (child instanceof ViewGroup) {
Log.d(TAG, "parseViewGroup: 该View不是FrameLayout,是ViewGroup: " + child
.getClass().getName());
parseViewGroup((ViewGroup) child, l, t, oldl, oldt, false,
child.getTop() + getTop);
}
continue;
}
//以下为执行动画逻辑;
SherlockAnimationCallBack myCallBack = (SherlockAnimationCallBack) child;
//获取子View高度;
int childHeight = child.getHeight();
//子控件到父控件的距离;
int childTop = child.getTop();
if (!isRootLinearLayout) {
childTop += getTop;
}
//滚动过程中,子View距离父控件顶部距离;
int childAbsluteTop = childTop - t;
//进入了屏幕
if (childAbsluteTop <= scrollViewHeight) {
//当前子控件显示出来的高度;
int childShowHeight = scrollViewHeight - childAbsluteTop - 100 ;
float moveRadio = childShowHeight / (float) childHeight;//这里一定要转化成float类型;
//执行动画;
myCallBack.excuteanimation(getMiddleValue(moveRadio, 0, 1));
} else {
//没在屏幕内,恢复数据;
myCallBack.resetViewanimation();
}
}
}
有了事件的分发了。我们只需要在excuteanimation
的回调中实现我们的动画即可了。默认的SherlockFrame已经实现了一些了。若要强大的自定义动画效果,实现这个接口即可。
public interface SherlockAnimationCallBack {
/**
* 执行自定义动画方法;
*/
void excuteanimation(float moveRadio);
/**
* 恢复初始状态;
*/
void resetViewanimation();
}
Copyright 2017 Jerey-Jobs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.