編輯:關於Android編程
github地址:https://github.com/niniloveyou/ScaleLayout
首先講講大概的思路:
就是我們要有三個View 分別為TopView CenterView bottomView 這很好理解,故名思義就是把這三個子View分別放在ViewGroup的上中下。
OnMeasure()中把CenterView的大小設置為等同於自身的大小
onLayout() 獲取topview bottomView的高度,根據高度設置當centerView縮小時topView/BottomView位移距離
onInterceptTouchEvent() 只處理滑動沖突部分。
onTouchEvent()中才是真正滑動縮小或放大實現的部分。
我們先貼代碼,後面緊跟著解釋:
@Override protected void onFinishInflate() { super.onFinishInflate(); int childCount = getChildCount(); if(childCount
/** * 使得centerView 大小等同ScaleLayout的大小 * 如果不想這樣處理,也可以在觸摸事件中使用TouchDelegate * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int widthSize = MeasureSpec.getSize(widthMeasureSpec); final int heightSize = MeasureSpec.getSize(heightMeasureSpec); int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight(); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(layoutWidth, MeasureSpec.EXACTLY); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(layoutHeight, MeasureSpec.EXACTLY); mCenterView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if(mBottomView != null){ mBottomViewMoveDistance = mBottomView.getMeasuredHeight(); } if(mTopView != null){ mTopViewMoveDistance = mTopView.getMeasuredHeight(); } if(mSuggestScaleEnable){ setMinScale(getSuggestScale()); } }
很簡單,只說一點:mBottomViewMoveDistance, mTopViewMoveDistance 分別為bottomView, topView動畫時位移的距離。
重點來了這個也是核心部分了。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercept = false; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: onTouchEvent(ev); mInitialMotionX = ev.getX(); mInitialMotionY = ev.getY(); break; case MotionEvent.ACTION_MOVE: final float deltaX = Math.abs(ev.getX() - mInitialMotionX); final float deltaY = Math.abs(ev.getY() - mInitialMotionY); if(mCanScaleListener != null && !mCanScaleListener.onGetCanScale(ev.getX() - mInitialMotionX > 0)){ intercept = false; }else { intercept = deltaY > deltaX && deltaY > mTouchSlop; } break; } return intercept; }
所有的down事件都不攔截,因此接下來的move, up事件,
都會先執行onInterceptTouchEvent的(move, up)
繼而分發給子view的dispatchTouchEvent(move, up),
因此在onInterceptTouchEvent(move)事件中我們可以判斷是否滿足滑動條件,滿足就攔截,攔截了之後move up事件就會都分發給自身的OnTouchEvent, 否則如上繼續分發給子View.
intercept = deltaY > deltaX && deltaY > mTouchSlop;
即Y位移的距離大於X方向 ,並且Y方向位移的距離大於TouchSlop,則認為這是有效滑動。
/** * 返回是否可以scale,主要為了適配部分有滑動沖突的view * 如TouchImageView, 甚至webView等 * isScrollSown = true 代表向下, * isScrollSown = false 代表向上 */ public interface OnGetCanScaleListener{ boolean onGetCanScale(boolean isScrollSown); }
if(mCanScaleListener != null && !mCanScaleListener.onGetCanScale(ev.getX() - mInitialMotionX > 0)){ intercept = false; }
這下明白了吧,我是做了個接口,要不要攔截由你說了算,也算我偷懶了。
/** * 該方法中實現了 * 上滑縮小下滑放大功能 * 也可設置為 上滑放大下滑縮小 * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { if (!isEnabled() || !mSlideScaleEnable) { return super.onTouchEvent(ev); } switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: downY = ev.getY(); return true; case MotionEvent.ACTION_MOVE: if(mCanScaleListener != null && !mCanScaleListener.onGetCanScale(ev.getY() - downY > 0)){ return super.onTouchEvent(ev); } if (Math.abs(ev.getY() - downY) > mTouchSlop) { mSlopLength += (ev.getY() - downY); float scale; if (mSlideUpOrDownEnable) { scale = 1 + (0.8f * mSlopLength / getMeasuredHeight()); } else { scale = 1 - (0.8f * mSlopLength / getMeasuredHeight()); } scale = Math.min(scale, 1f); mCurrentScale = Math.max(mMinScale, scale); doSetScale(); downY = ev.getY(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mCurrentScale > mMinScale && mCurrentScale < 1f) { float half = (1 - mMinScale) / 2; if (mCurrentScale >= mMinScale + half) { setState(STATE_CLOSE, true); } else { setState(STATE_OPEN, true); } } break; } return super.onTouchEvent(ev); }
這部分,首先是move的時候用mSlopLength計算滑動的距離向下滑就加正值,向上劃值就減小,不斷根據這個值計算當前的Scale. 應該縮放的比例,然後根據這個值計算topView bottomView 的透明度,位移距離,等等, 當UP的時候,根據當前的Scale決定是應該放大到原寬高還是縮小,以動畫的形式。
···
/**
* 1.觸發監聽事件
* 2.計算scale的pivotX, pivotY(因為topView 和bottomView 的高度可能不一樣,所以不能固定設置在中心點)
* 3.設置 mCenterView的scale
* 4.設置topView and BottomView 的動畫(漸變和位移)
*/
private void doSetScale() {
int scaleListenerCount = mScaleListenerList.size();
OnScaleChangedListener mScaleChangedListener;
for (int i = 0; i < scaleListenerCount; i++) {
mScaleChangedListener = mScaleListenerList.get(i);
if(mScaleChangedListener != null){
mScaleChangedListener.onScaleChanged(mCurrentScale);
}
}
if(mCurrentScale == mMinScale || mCurrentScale == 1f){
int stateListenerCount = mStateListenerList.size();
OnStateChangedListener mStateChangedListener;
for (int i = 0; i < stateListenerCount; i++) {
mStateChangedListener = mStateListenerList.get(i);
if(mStateChangedListener != null){
mStateChangedListener.onStateChanged(mCurrentScale == mMinScale);
}
}
}
doSetCenterView(mCurrentScale);
doSetTopAndBottomView(mCurrentScale);
}
···
我把監聽事件也貼上:
/**
* 當centerView 的scale變化的時候,通過這個
* 接口外部的View可以做一些同步的事情,
* 比如,你有一個其他的view要根據centerView的變化而變化
*/
public interface OnScaleChangedListener{
void onScaleChanged(float currentScale);
}
/**
* state == false 當完全關閉(scale == 1f)
* state == true 或當完全開啟的時候(scale = mMinScale)
*/
public interface OnStateChangedListener{
void onStateChanged(boolean state);
}
4.對外提供方法和接口
關於接口,代碼我都無恥的貼上去了。
下面說說提供的幾個簡單的外部方法:
/**
* 設置最小scale
* {@link #DEFAULT_MIN_SCALE}
* @param minScale
*/
public void setMinScale(float minScale){
if(minScale > 0f && minScale < 1f){
if(mMinScale != minScale){
if(isOpen()){
if(animator != null){
animator.cancel();
animator = null;
}
animator = getAnimator(mMinScale, minScale);
animator.start();
}
mMinScale = minScale;
}
}
}
public float getMinScale(){
return mMinScale;
}
public float getCurrentScale(){
return mCurrentScale;
}
public void setSuggestScaleEnable(boolean enable){
if(mSuggestScaleEnable != enable){
mSuggestScaleEnable = enable;
requestLayout();
}
}
/**
* 設置的scale不得當的話,有可能topView / bottomView被覆蓋
* 通過設置{@link #setSuggestScaleEnable(boolean)}啟用
* @return
*/
private float getSuggestScale(){
int height = 0;
if(mTopView != null){
height += mTopView.getMeasuredHeight();
}
if(mBottomView != null){
height += mBottomView.getMeasuredHeight();
}
return 1 - height * 1f / (getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
}
/**
* 設置是否啟用滑動縮小功能
* @param enable
*/
public void setSlideScaleEnable(boolean enable){
this.mSlideScaleEnable = enable;
}
/**
* 現在有這麼幾種情況, 默認第二種, 兩者都可以的話,感覺好奇怪,
* 比如一直下滑會由大變小後又變大,操作感覺不是很好
* 1. 只上滑放大下滑縮小 false
* 2. 只上滑縮小下滑放大 true
*/
public void setSlideUpOrDownEnable(boolean enable){
this.mSlideUpOrDownEnable = enable;
}
/**
* add OnScaleChangedListener
* @param listener
*/
public void addOnScaleChangedListener(OnScaleChangedListener listener){
if(listener != null){
mScaleListenerList.add(listener);
}
}
/**
* add OnStateChangedListener
* @param listener
*/
public void addOnStateChangedListener(OnStateChangedListener listener){
if(listener != null){
mStateListenerList.add(listener);
}
}
public void setOnGetCanScaleListener(OnGetCanScaleListener listener){
mCanScaleListener = listener;
}
/**
* {@link #setState(int state, boolean animationEnable)}
* @param state
*/
public void setState(int state){
setState(state, true);
}
/**
* 設置狀態變化
* @param state open or close
* @param animationEnable change state with or without animation
*/
public void setState(final int state, boolean animationEnable) {
if(!animationEnable)
{
if(state == STATE_CLOSE){
mSlopLength = 0;
mCurrentScale = 1;
}else{
if(mSlideUpOrDownEnable) {
mSlopLength = -getMeasuredHeight() * (1 - mMinScale) * 1.25f;
}else{
mSlopLength = getMeasuredHeight() * (1 - mMinScale) * 1.25f;
}
mCurrentScale = mMinScale;
}
doSetScale();
mState = state;
}else{
if(animator != null){
animator.cancel();
animator = null;
}
if(state == STATE_CLOSE && mCurrentScale != 1){
mSlopLength = 0;
animator = getAnimator(mCurrentScale, 1f);
}else if(state == STATE_OPEN && mCurrentScale != mMinScale){
if(mSlideUpOrDownEnable) {
mSlopLength = -getMeasuredHeight() * (1 - mMinScale) * 1.25f;
}else{
mSlopLength = getMeasuredHeight() * (1 - mMinScale) * 1.25f;
}
animator = getAnimator(mCurrentScale, mMinScale);
}
if(animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mState = state;
}
});
animator.start();
}
}
}
/**
* 獲取當前狀態開啟或者關閉
* @return
*/
public boolean isOpen(){
return mState == STATE_OPEN;
}
代碼貼完了。
如果感覺還行,到我的github star一下吧。 謝謝!
https://github.com/niniloveyou/ScaleLayout
android中提供了4中動畫: AlphaAnimation 透明度動畫效果 ScaleAnimation 縮放動畫效果 TranslateAnimation 位移動畫
效果如下: 此圖片不會動,但實際上是會快速跳動的。 之前看到有支付寶的效果非常牛逼。就是進去看到余額呼噜噜的直接上躥下跳到具體數字,效果帥,
寫在前面一家移動互聯網公司,說到底,要盈利總是需要付費用戶的,自己開發支付系統顯然是不明智的,國內已經有多家成熟的移動支付提供商,騰訊就是其中之一。梳理了下微信支付的接入
一開始接觸android,只知道拖拉布局,不靈活,難看很。後來才發現,原來和jsp一樣,可以靈活使用xml文件。 話不多說,直接上例子 任何布局既可以在代碼中實現,也可以