/** The MIT License (MIT) Copyright (c) 2014 singwhatiwanna https://github.com/singwhatiwanna http://blog.csdn.net/singwhatiwanna Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.todayinfo.ui.component; import java.util.NoSuchElementException; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.LinearLayout; public class StickyLayout extends LinearLayout { private static final String TAG = "StickyLayout"; private static final boolean DEBUG = true; public interface OnGiveUpTouchEventListener { public boolean giveUpTouchEvent(MotionEvent event); } /** * 正在滑动的监听 * @author longtao.li * */ public interface OnTouchEventMoveListenre{ /** * 上滑触发函数 * @param mOriginalHeaderHeight 原始高度 * @param mHeaderHeight 现在高度 */ public void onSlideUp(int mOriginalHeaderHeight, int mHeaderHeight); /** * 下滑触发函数 * @param mOriginalHeaderHeight * @param mHeaderHeight */ public void onSlideDwon(int mOriginalHeaderHeight, int mHeaderHeight); /** * 对应的透明度(用于顶部工具栏透明度的定制) * @param alpha */ public void onSlide(int alpha); } private View mHeader; private View mContent; private OnGiveUpTouchEventListener mGiveUpTouchEventListener; private OnTouchEventMoveListenre mOnTouchEventMoveListenre; // header的高度 单位:px private int mOriginalHeaderHeight; private int mHeaderHeight; private int mStatus = STATUS_EXPANDED; public static final int STATUS_EXPANDED = 1; public static final int STATUS_COLLAPSED = 2; private int mTouchSlop; // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; // 分别记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2 private static final int TAN = 2; private boolean mIsSticky = true; private boolean mInitDataSucceed = false; private boolean mDisallowInterceptTouchEventOnHeader = true; private int mAlpha = 0;//透明度 private static final float MAX_ALPHA = 225.00000f; public StickyLayout(Context context) { super(context); } public StickyLayout(Context context, AttributeSet attrs) { super(context, attrs); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public StickyLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus && (mHeader == null || mContent == null)) { initData(); } } private void initData() { int headerId= getResources().getIdentifier("sticky_header", "id", getContext().getPackageName()); int contentId = getResources().getIdentifier("sticky_content", "id", getContext().getPackageName()); if (headerId != 0 && contentId != 0) { mHeader = findViewById(headerId); mContent = findViewById(contentId); mOriginalHeaderHeight = mHeader.getMeasuredHeight(); mHeaderHeight = mOriginalHeaderHeight; mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); if (mHeaderHeight > 0) { mInitDataSucceed = true; } if (DEBUG) { Log.d(TAG, "mTouchSlop = " + mTouchSlop + "mHeaderHeight = " + mHeaderHeight); } } else { throw new NoSuchElementException("Did your view with id \"sticky_header\" or \"sticky_content\" exists?"); } } public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) { mGiveUpTouchEventListener = l; } public void setOnTouchEventMoveListenre(OnTouchEventMoveListenre l) { mOnTouchEventMoveListenre = l; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { int intercepted = 0; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mLastXIntercept = x; mLastYIntercept = y; mLastX = x; mLastY = y; intercepted = 0; break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (mDisallowInterceptTouchEventOnHeader && y <= getHeaderHeight()) { intercepted = 0; } else if (Math.abs(deltaY) <= Math.abs(deltaX)) { intercepted = 0; } else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) { intercepted = 1; } else if (mGiveUpTouchEventListener != null) { if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) { intercepted = 1; } } break; } case MotionEvent.ACTION_UP: { intercepted = 0; mLastXIntercept = mLastYIntercept = 0; break; } default: break; } if (DEBUG) { Log.d(TAG, "intercepted=" + intercepted); } return intercepted != 0 && mIsSticky; } @Override public boolean onTouchEvent(MotionEvent event) { if (!mIsSticky) { return true; } int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (DEBUG) { Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY); } mHeaderHeight += deltaY; setHeaderHeight(mHeaderHeight); if( mOnTouchEventMoveListenre != null ){ float offsetY = y - event.getY(); if (mOriginalHeaderHeight == mHeaderHeight) { mAlpha = 0; } else if (mHeaderHeight <= 0) { mAlpha = (int) MAX_ALPHA; } else { float d = 225.000f / (float) mOriginalHeaderHeight; double alpha = d * mHeaderHeight; if (offsetY > 0) { // 下滑 mAlpha = (int) alpha; mOnTouchEventMoveListenre.onSlideDwon(mOriginalHeaderHeight, mHeaderHeight); } else if (offsetY < 0) { // 上滑 mAlpha = (int) (MAX_ALPHA - alpha); mOnTouchEventMoveListenre.onSlideUp(mOriginalHeaderHeight, mHeaderHeight); } mOnTouchEventMoveListenre.onSlide(mAlpha); } } break; } case MotionEvent.ACTION_UP: { // // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置 // int destHeight = 0; // if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) { // destHeight = 0; // mStatus = STATUS_COLLAPSED; // } else { // destHeight = mOriginalHeaderHeight; // mStatus = STATUS_EXPANDED; // } // // 慢慢滑向终点 // this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500); break; } default: break; } mLastX = x; mLastY = y; return true; } public void smoothSetHeaderHeight(final int from, final int to, long duration) { smoothSetHeaderHeight(from, to, duration, false); } public void smoothSetHeaderHeight(final int from, final int to, long duration, final boolean modifyOriginalHeaderHeight) { final int frameCount = (int) (duration / 1000f * 30) + 1; final float partation = (to - from) / (float) frameCount; new Thread("Thread#smoothSetHeaderHeight") { @Override public void run() { for (int i = 0; i < frameCount; i++) { final int height; if (i == frameCount - 1) { height = to; } else { height = (int) (from + partation * i); } post(new Runnable() { public void run() { setHeaderHeight(height); } }); try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } if (modifyOriginalHeaderHeight) { setOriginalHeaderHeight(to); } }; }.start(); } public void setOriginalHeaderHeight(int originalHeaderHeight) { mOriginalHeaderHeight = originalHeaderHeight; } public void setHeaderHeight(int height, boolean modifyOriginalHeaderHeight) { if (modifyOriginalHeaderHeight) { setOriginalHeaderHeight(height); } setHeaderHeight(height); } public void setHeaderHeight(int height) { if (!mInitDataSucceed) { initData(); } if (DEBUG) { Log.d(TAG, "setHeaderHeight height=" + height); } if (height <= 0) { height = 0; } else if (height > mOriginalHeaderHeight) { height = mOriginalHeaderHeight; } if (height == 0) { mStatus = STATUS_COLLAPSED; } else { mStatus = STATUS_EXPANDED; } if (mHeader != null && mHeader.getLayoutParams() != null) { mHeader.getLayoutParams().height = height; mHeader.requestLayout(); mHeaderHeight = height; } else { if (DEBUG) { Log.e(TAG, "null LayoutParams when setHeaderHeight"); } } } public int getHeaderHeight() { return mHeaderHeight; } public void setSticky(boolean isSticky) { mIsSticky = isSticky; } public void requestDisallowInterceptTouchEventOnHeader(boolean disallowIntercept) { mDisallowInterceptTouchEventOnHeader = disallowIntercept; } }