- 侧边菜单栏
- 侧边菜单栏动画
- fragment 切换动画(另一个开源项目ozodrukh/CircularReveal)
- 动画是用到了nineoldandroids 开源项目
- 侧边菜单栏实现
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--主体内容-->
<io.codetail.widget.RevealFrameLayout
android:id="@+id/container_frame"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--上一个fragment视图的截屏-->
<LinearLayout
android:id="@+id/content_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<!--fragment容器-->
<LinearLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<!--标题栏-->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
</io.codetail.widget.RevealFrameLayout>
<!--菜单-->
<ScrollView
android:id="@+id/scrollView"
android:scrollbarThumbVertical="@android:color/transparent"
android:layout_width="@dimen/sliding_menu_width"
android:layout_height="match_parent"
android:layout_gravity="start|bottom">
<LinearLayout
android:id="@+id/left_drawer"
android:orientation="vertical"
android:layout_width="@dimen/sliding_menu_width"
android:layout_height="wrap_content"
android:divider="@android:color/transparent"
android:background="@android:color/transparent"/>
</ScrollView>
</android.support.v4.widget.DrawerLayout>
菜单栏中的内容通过addViewToContainer回调的方式添加到
left_drawer
中
//MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
//....
linearLayout = ( LinearLayout ) findViewById ( R . id . left_drawer );// list 为菜单每个项的内容// contentFragment 为主体显示继承自Fragment 并实现了ScreenShotable接口// 最后一个参数为ViewAnimator.ViewAnimatorListener 接口,其中一个方法便是addViewToContainer// 在ViewAnimator 中 创建view 并添加到 linearLayout 菜单中.viewAnimator = new ViewAnimator < SlideMenuItem >( this , list , contentFragment , drawerLayout , this );}
//添加到菜单的LinearLayout 中去
@Override
public void addViewToContainer(View view) {
linearLayout.addView(view);
}
ViewAnimator类, 用于菜单内容的创建, 菜单显示时候的动画效果, 以及菜单项的点击反馈.
//ViewAnimator.javapublic class ViewAnimator<T extends Resourceble> {
//显示菜单
public void showMenuContent() {
setViewsClickable(false);
//清除菜单项集合
viewList.clear();
double size = list.size();
for (int i = 0; i < size; i++) {
View viewMenu = actionBarActivity.getLayoutInflater().inflate(R.layout.menu_list_item, null);
final int finalI = i;
viewMenu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int[] location = {0, 0};
//得到当前view 在屏幕的位置
v.getLocationOnScreen(location);
//回调视图接口,当点击某一项的时候反馈到MainActivity.onSwitch()用于替换主体Fragment内容
switchItem(list.get(finalI), location[1] + v.getHeight() / 2);
}
});
((ImageView) viewMenu.findViewById(R.id.menu_item_image)).setImageResource(list.get(i).getImageRes());
viewMenu.setVisibility(View.GONE);
viewMenu.setEnabled(false);
//保存到集合
viewList.add(viewMenu);
//添加到菜单栏中,调用MainActivity.addViewToContainer(View).
animatorListener.addViewToContainer(viewMenu);
//以下是动画
final double position = i;
final double delay = 3 * ANIMATION_DURATION * (position / size);
new Handler().postDelayed(new Runnable() {
public void run() {
if (position < viewList.size()) {
animateView((int) position);
}
//当菜单显示完毕之后,调用ContentFragment.takeScreenShot()
//在ContentFragment.takeScreenShot()实现是类似于截屏,将自己的视图截屏,
//当然这是不包括左侧显示出来的菜单的.
if (position == viewList.size() - 1) {
screenShotable.takeScreenShot();
//将所有菜单项设置为可点击状态
setViewsClickable(true);
}
}
}, (long) delay);
}
}
private void setViewsClickable(boolean clickable) {
animatorListener.disableHomeButton();
for (View view : viewList) {
view.setEnabled(clickable);
}
}
private void animateView(int position) {
final View view = viewList.get(position);
view.setVisibility(View.VISIBLE);
FlipAnimation rotation =
new FlipAnimation(90, 0, 0.0f, view.getHeight() / 2.0f);
rotation.setDuration(ANIMATION_DURATION);
rotation.setFillAfter(true);
rotation.setInterpolator(new AccelerateInterpolator());
rotation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
view.clearAnimation();
}
});
view.startAnimation(rotation);
}
private void switchItem(Resourceble slideMenuItem, int topPosition) {
this.screenShotable = animatorListener.onSwitch(slideMenuItem, screenShotable, topPosition);
//隐藏菜单
hideMenuContent();
}
public interface ViewAnimatorListener {
//切换视图
//ScreenShotable为当前主体内容的截屏
public ScreenShotable onSwitch(Resourceble slideMenuItem, ScreenShotable screenShotable, int position);
public void disableHomeButton();
public void enableHomeButton();
public void addViewToContainer(View view);
}
}
视图切换动画实现
private ScreenShotable replaceFragment(ScreenShotable screenShotable, int topPosition) {
this.res = this.res == R.drawable.content_music ? R.drawable.content_films : R.drawable.content_music;
View view = findViewById(R.id.content_frame);
int finalRadius = Math.max(view.getWidth(), view.getHeight());
//创建圆形动画
SupportAnimator animator = ViewAnimationUtils.createCircularReveal(view, 0, topPosition, 0, finalRadius);
animator.setInterpolator(new AccelerateInterpolator());
animator.setDuration(ViewAnimator.CIRCULAR_REVEAL_ANIMATION_DURATION);
//由于圆形动画是一点点的扩大的,其没有全部覆盖的部分应该为上一个视图的内容,
// 因此我们需要将前面的视图截图保存下来,可将下面代码屏蔽可明白其意义.
findViewById(R.id.content_overlay).setBackgroundDrawable(new BitmapDrawable(getResources(), screenShotable.getBitmap()));
animator.start();
ContentFragment contentFragment = ContentFragment.newInstance(this.res);
getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, contentFragment).commit();
return contentFragment;
}
//MainActivity.java
//在MainActivity中设置了对ViewAnimator的监听,而在ViewAnimator内部也关联上菜单点击的响应switchItem()
@Override
public ScreenShotable onSwitch(Resourceble slideMenuItem, ScreenShotable screenShotable, int position) {
String s = slideMenuItem.getName();
if (s.equals(ContentFragment.CLOSE)) {
return screenShotable;
} else {
return replaceFragment(screenShotable, position);
}
}
创建圆形动画,由 ViewAnimationUtils 工具类实现,其中用到了
ozodrukh/CircularReveal
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static SupportAnimator createCircularReveal(View view,
int centerX, int centerY,
float startRadius, float endRadius) {
if(LOLLIPOP_PLUS){
return new SupportAnimatorLollipop(android.view.ViewAnimationUtils
.createCircularReveal(view, centerX, centerY, startRadius, endRadius));
}
if(!(view.getParent() instanceof RevealAnimator)){
throw new IllegalArgumentException("View must be inside RevealFrameLayout or RevealLinearLayout.");
}
//在布局文件中其父类为 io.codetail.widget.RevealFrameLayout,实现了RevealAnimator 接口
RevealAnimator revealLayout = (RevealAnimator) view.getParent();
revealLayout.setTarget(view);
revealLayout.setCenter(centerX, centerY);
Rect bounds = new Rect();
view.getHitRect(bounds);
//创建Object 动画
ObjectAnimator reveal = ObjectAnimator.ofFloat(revealLayout, "revealRadius", startRadius, endRadius);
reveal.addListener(getRevealFinishListener(revealLayout, bounds));
return new SupportAnimatorPreL(reveal);
}
package io.codetail.widget;
public class RevealFrameLayout extends FrameLayout implements RevealAnimator{
//设置需要圆形动画的view
@Override
public void setTarget(View view){
mTarget = view;
}
//圆心设置
@Override
public void setCenter(float centerX, float centerY){
mCenterX = centerX;
mCenterY = centerY;
}
//会自动调用,将其从 startRadius递增至 endRadius@Override
public void setRevealRadius(float radius){
mRadius = radius;
invalidate();
}
@Override
public float getRevealRadius(){
return mRadius;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if(!mClipOutlines && child != mTarget)
return super.drawChild(canvas, child, drawingTime);
//动画实现
final int state = canvas.save();
mRevealPath.reset();
mRevealPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);
canvas.clipPath(mRevealPath);
boolean isInvalided = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(state);
return isInvalided;
}
}