diff --git a/circularanim/build.gradle b/circularanim/build.gradle index 3c637a2..db28b76 100644 --- a/circularanim/build.gradle +++ b/circularanim/build.gradle @@ -10,7 +10,7 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 24 - versionCode 3 + versionCode 4 versionName "0.2.4" } buildTypes { diff --git a/circularanim/src/main/java/top/wefor/circularanim/CircularAnim.java b/circularanim/src/main/java/top/wefor/circularanim/CircularAnim.java new file mode 100644 index 0000000..0f8b2ae --- /dev/null +++ b/circularanim/src/main/java/top/wefor/circularanim/CircularAnim.java @@ -0,0 +1,290 @@ +package top.wefor.circularanim; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.support.annotation.DrawableRes; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewGroup; +import android.widget.ImageView; + +/** + * Created on 16/8/5. + *
+ * 对 ViewAnimationUtils.createCircularReveal() 方法的封装. + * + * GitHub: https://github.com/XunMengWinter + * + * latest edited date: 2016-08-05 17:07 + * + * @author ice + */ +public class CircularAnim { + + public static final long PERFECT_MILLS = 618; + public static final int MINI_RADIUS = 0; + + public interface OnAnimationEndListener { + void onAnimationEnd(); + } + + public static VisibleBuilder show(View animView) { + return new VisibleBuilder(animView, true); + } + + public static VisibleBuilder hide(View animView) { + return new VisibleBuilder(animView, false); + } + + public static FullActivityBuilder fullActivity(Activity activity, + View triggerView, + @DrawableRes int colorOrImageRes, + OnAnimationEndListener onAnimationEndListener) { + return new FullActivityBuilder(activity, triggerView, colorOrImageRes, onAnimationEndListener); + } + + @SuppressLint("NewApi") + public static class VisibleBuilder { + private View mAnimView, mTriggerView; + + private Float mStartRadius, mEndRadius; + + private long mDurationMills = PERFECT_MILLS; + + private boolean isShow; + + private OnAnimationEndListener mOnAnimationEndListener; + + public VisibleBuilder(View animView, boolean isShow) { + mAnimView = animView; + this.isShow = isShow; + + if (isShow) { + mStartRadius = MINI_RADIUS + 0F; + } else { + mEndRadius = MINI_RADIUS + 0F; + } + } + + public VisibleBuilder triggerView(View triggerView) { + mTriggerView = triggerView; + return this; + } + + public VisibleBuilder startRadius(float startRadius) { + mStartRadius = startRadius; + return this; + } + + public VisibleBuilder endRadius(float endRadius) { + mEndRadius = endRadius; + return this; + } + + public VisibleBuilder duration(long durationMills) { + mDurationMills = durationMills; + return this; + } + + public VisibleBuilder onAnimationEndListener(OnAnimationEndListener onAnimationEndListener) { + mOnAnimationEndListener = onAnimationEndListener; + return this; + } + + public void go() { + // 版本判断 + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + if (isShow) + mAnimView.setVisibility(View.VISIBLE); + else + mAnimView.setVisibility(View.INVISIBLE); + + if (mOnAnimationEndListener != null) + mOnAnimationEndListener.onAnimationEnd(); + return; + } + + int rippleCX, rippleCY, maxRadius; + if (mTriggerView != null) { + int[] tvLocation = new int[2]; + mTriggerView.getLocationInWindow(tvLocation); + final int tvCX = tvLocation[0] + mTriggerView.getWidth() / 2; + final int tvCY = tvLocation[1] + mTriggerView.getHeight() / 2; + + int[] avLocation = new int[2]; + mAnimView.getLocationInWindow(avLocation); + final int avLX = avLocation[0]; + final int avTY = avLocation[1]; + + int triggerX = Math.max(avLX, tvCX); + triggerX = Math.min(triggerX, avLX + mAnimView.getWidth()); + + int triggerY = Math.max(avTY, tvCY); + triggerY = Math.min(triggerY, avTY + mAnimView.getHeight()); + + // 以上全为绝对坐标 + + int avW = mAnimView.getWidth(); + int avH = mAnimView.getHeight(); + + rippleCX = triggerX - avLX; + rippleCY = triggerY - avTY; + + // 计算水波中心点至 @mAnimView 边界的最大距离 + int maxW = Math.max(rippleCX, avW - rippleCX); + int maxH = Math.max(rippleCY, avH - rippleCY); + maxRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1; + } else { + rippleCX = (mAnimView.getLeft() + mAnimView.getRight()) / 2; + rippleCY = (mAnimView.getTop() + mAnimView.getBottom()) / 2; + + int w = mAnimView.getWidth(); + int h = mAnimView.getHeight(); + + // 勾股定理 & 进一法 + maxRadius = (int) Math.sqrt(w * w + h * h) + 1; + } + + if (isShow && mEndRadius == null) + mEndRadius = maxRadius + 0F; + else if (!isShow && mStartRadius == null) + mStartRadius = maxRadius + 0F; + + Animator anim = ViewAnimationUtils.createCircularReveal( + mAnimView, rippleCX, rippleCY, mStartRadius, mEndRadius); + mAnimView.setVisibility(View.VISIBLE); + anim.setDuration(mDurationMills); + + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (isShow) + mAnimView.setVisibility(View.VISIBLE); + else + mAnimView.setVisibility(View.INVISIBLE); + + if (mOnAnimationEndListener != null) + mOnAnimationEndListener.onAnimationEnd(); + } + }); + + anim.start(); + } + + } + + @SuppressLint("NewApi") + public static class FullActivityBuilder { + private Activity mActivity; + private View mTriggerView; + private float mStartRadius = MINI_RADIUS; + @DrawableRes + private int mColorOrImageRes; + private Long mDurationMills; + private OnAnimationEndListener mOnAnimationEndListener; + private int mEnterAnim = android.R.anim.fade_in, mExitAnim = android.R.anim.fade_out; + + public FullActivityBuilder(Activity activity, + View triggerView, + @DrawableRes int colorOrImageRes, + OnAnimationEndListener onAnimationEndListener) { + mActivity = activity; + mTriggerView = triggerView; + mColorOrImageRes = colorOrImageRes; + mOnAnimationEndListener = onAnimationEndListener; + } + + public FullActivityBuilder startRadius(float startRadius) { + mStartRadius = startRadius; + return this; + } + + public FullActivityBuilder duration(long durationMills) { + mDurationMills = durationMills; + return this; + } + + public FullActivityBuilder overridePendingTransition(int enterAnim, int exitAnim) { + mEnterAnim = enterAnim; + mExitAnim = exitAnim; + return this; + } + + public void go() { + // 版本判断,小于5.0则无动画. + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + mOnAnimationEndListener.onAnimationEnd(); + return; + } + + int[] location = new int[2]; + mTriggerView.getLocationInWindow(location); + final int cx = location[0] + mTriggerView.getWidth() / 2; + final int cy = location[1] + mTriggerView.getHeight() / 2; + final ImageView view = new ImageView(mActivity); + view.setScaleType(ImageView.ScaleType.CENTER_CROP); + view.setImageResource(mColorOrImageRes); + final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView(); + int w = decorView.getWidth(); + int h = decorView.getHeight(); + decorView.addView(view, w, h); + + // 计算中心点至view边界的最大距离 + int maxW = Math.max(cx, w - cx); + int maxH = Math.max(cy, h - cy); + final int finalRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1; + + Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, mStartRadius, finalRadius); + int maxRadius = (int) Math.sqrt(w * w + h * h) + 1; + // 若未设置时长,则以PERFECT_MILLS为基准根据水波扩散的距离来计算实际时间 + if (mDurationMills == null) { + // 算出实际边距与最大边距的比率 + double rate = 1d * finalRadius / maxRadius; + // 为了让用户便于感触到水波,速度应随最大边距的变小而越慢,扩散时间应随最大边距的变小而变小,因此比率应在 @rate 与 1 之间。 + mDurationMills = (long) (PERFECT_MILLS * Math.sqrt(rate)); + } + final long finalDuration = mDurationMills; + // 由于thisActivity.startActivity()会有所停顿,所以进入的水波动画应比退出的水波动画时间短才能保持视觉上的一致。 + anim.setDuration((long) (finalDuration * 0.9)); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + + mOnAnimationEndListener.onAnimationEnd(); + + // 默认渐隐过渡动画. + mActivity.overridePendingTransition(mEnterAnim, mExitAnim); + + // 默认显示返回至当前Activity的动画. + mTriggerView.postDelayed(new Runnable() { + @Override + public void run() { + Animator anim = + ViewAnimationUtils.createCircularReveal(view, cx, cy, finalRadius, mStartRadius); + anim.setDuration(finalDuration); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + try { + decorView.removeView(view); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + anim.start(); + } + }, 1000); + + } + }); + anim.start(); + } + } + +} diff --git a/circularanim/src/main/java/top/wefor/circularanim/CircularAnimUtil.java b/circularanim/src/main/java/top/wefor/circularanim/CircularAnimUtil.java index fcb2e8f..1b20569 100644 --- a/circularanim/src/main/java/top/wefor/circularanim/CircularAnimUtil.java +++ b/circularanim/src/main/java/top/wefor/circularanim/CircularAnimUtil.java @@ -20,6 +20,7 @@ * * @author ice */ +@Deprecated //this class was replaced by CircularAnim.class public class CircularAnimUtil { public static final long PERFECT_MILLS = 618; diff --git a/demo/build.gradle b/demo/build.gradle index ac253fb..35355cc 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -8,8 +8,8 @@ android { applicationId "top.wefor.circularanim" minSdkVersion 14 targetSdkVersion 24 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "1.1" } buildTypes { release { diff --git a/demo/src/main/java/top/wefor/circularanimdemo/MainActivity.java b/demo/src/main/java/top/wefor/circularanimdemo/MainActivity.java index 1d68f35..a98eb50 100644 --- a/demo/src/main/java/top/wefor/circularanimdemo/MainActivity.java +++ b/demo/src/main/java/top/wefor/circularanimdemo/MainActivity.java @@ -3,6 +3,7 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; @@ -10,7 +11,7 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; -import top.wefor.circularanim.CircularAnimUtil; +import top.wefor.circularanim.CircularAnim; public class MainActivity extends AppCompatActivity { @@ -33,13 +34,17 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { mLogoBtnIv = (ImageView) findViewById(R.id.logoBtn_iv); mContentLayout = (LinearLayout) findViewById(R.id.content_layout); + CircularAnim.show(null); + new AlertDialog.Builder(this).create().show(); + mChangeBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mChangeBtn.setEnabled(false); mProgressBar.setVisibility(View.VISIBLE); // 收缩按钮 - CircularAnimUtil.hide(mChangeBtn); + CircularAnim.hide(mChangeBtn).go(); +// CircularAnimUtil.hide(mChangeBtn); } }); @@ -49,7 +54,8 @@ public void onClick(View view) { mProgressBar.setVisibility(View.GONE); mChangeBtn.setEnabled(true); // 伸展按钮 - CircularAnimUtil.show(mChangeBtn); + CircularAnim.show(mChangeBtn).go(); +// CircularAnimUtil.show(mChangeBtn); } }); @@ -57,7 +63,14 @@ public void onClick(View view) { @Override public void onClick(View view) { // 先将图片展出铺满,然后启动新的Activity - CircularAnimUtil.startActivity(MainActivity.this, EmptyActivity.class, view, R.mipmap.img_huoer_black); + CircularAnim.fullActivity(MainActivity.this, view, R.mipmap.img_huoer_black, + new CircularAnim.OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + startActivity(new Intent(MainActivity.this, EmptyActivity.class)); + } + }).go(); +// CircularAnimUtil.startActivity(MainActivity.this, EmptyActivity.class, view, R.mipmap.img_huoer_black); } }); @@ -65,7 +78,14 @@ public void onClick(View view) { @Override public void onClick(View view) { // 先将颜色展出铺满,然后启动新的Activity - CircularAnimUtil.startActivity(MainActivity.this, EmptyActivity.class, view, R.color.colorPrimary); + CircularAnim.fullActivity(MainActivity.this, view, R.color.colorPrimary, + new CircularAnim.OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + startActivity(new Intent(MainActivity.this, EmptyActivity.class)); + } + }).go(); +// CircularAnimUtil.startActivity(MainActivity.this, EmptyActivity.class, view, R.color.colorPrimary); } }); @@ -73,8 +93,16 @@ public void onClick(View view) { @Override public void onClick(View view) { // 先将颜色展出铺满,然后启动新的Activity并finish当前Activity. - Intent intent = new Intent(MainActivity.this, EmptyActivity.class); - CircularAnimUtil.startActivityThenFinish(MainActivity.this, intent, view, R.color.colorPrimary); + CircularAnim.fullActivity(MainActivity.this, view, R.color.colorPrimary, + new CircularAnim.OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + startActivity(new Intent(MainActivity.this, EmptyActivity.class)); + finish(); + } + }).go(); +// Intent intent = new Intent(MainActivity.this, EmptyActivity.class); +// CircularAnimUtil.startActivityThenFinish(MainActivity.this, intent, view, R.color.colorPrimary); } }); @@ -83,10 +111,17 @@ public void onClick(View view) { public void onClick(View view) { view.animate().rotationBy(90); // 以 @mLogoBtnIv 为中心,收缩或伸展 @mContentLayout - if (isContentVisible) - CircularAnimUtil.hideOther(mLogoBtnIv, mContentLayout); - else - CircularAnimUtil.showOther(mLogoBtnIv, mContentLayout); + if (isContentVisible) { + CircularAnim.hide(mContentLayout) + .triggerView(mLogoBtnIv) + .go(); +// CircularAnimUtil.hideOther(mLogoBtnIv, mContentLayout); + } else { + CircularAnim.show(mContentLayout) + .triggerView(mLogoBtnIv) + .go(); +// CircularAnimUtil.showOther(mLogoBtnIv, mContentLayout); + } isContentVisible = !isContentVisible; }