滑动抽屉
[java] view plaincopyprint? package com.pepper.panel; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.Gravity; import android.view.ViewGroup.LayoutParams; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.pepper.panel.HttpClientTast.OnCompleteListener; public class MainActivity extends Activity { private Context mContext; private LinearLayout mPanelContent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; mPanelContent = (LinearLayout) findViewById(R.id.panelContentLl); createImageView(); } private void createImageView() { for (int i = 0; i < 20; i++) { LinearLayout ll = new LinearLayout(mContext); ll.setOrientation(LinearLayout.VERTICAL); setParams(ll); mPanelContent.addView(ll); TextView text = new TextView(mContext); text.setTextColor(Color.WHITE); text.setText("button" + i); text.setTextSize(10); setParams(text); ImageView iconImageView = new ImageView(mContext); iconImageView.setBackgroundResource(R.drawable.ic_launcher); setParams(iconImageView); ll.addView(iconImageView); ll.addView(text); } } // 可在该方法里进行属性参数的相关设置 private void setParams(LinearLayout ll) { // 参数:按钮的宽高 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT); params.weight = 1.0f;// 重量级 params.setMargins(60, 20, 60, 20); ll.setLayoutParams(params); } private void setParams(TextView text) { // 参数:按钮的宽高 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_HORIZONTAL;// 重心 text.setLayoutParams(params); } private void setParams(ImageView image) { // 参数:按钮的宽高 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( getIntFromDimens(R.dimen.foot_bar_w), getIntFromDimens(R.dimen.foot_bar_h)); params.gravity = Gravity.CENTER_HORIZONTAL;// 重心 image.setLayoutParams(params); } public int getIntFromDimens(int index) { int result = this.getResources().getDimensionPixelSize(index); return result; } }
package com.pepper.panel; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.OnGestureListener; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; public class Panel extends LinearLayout { /** * Callback invoked when the panel is opened/closed. */ public static interface OnPanelListener { /** * Invoked when the panel becomes fully closed. */ public void onPanelClosed(Panel panel); /** * Invoked when the panel becomes fully opened. */ public void onPanelOpened(Panel panel); } class PanelOnGestureListener implements OnGestureListener { float scrollY; float scrollX; private float ensureRange(float v, int min, int max) { v = Math.max(v, min); v = Math.min(v, max); return v; } public boolean initChange() { if (mState != State.READY) { // we are animating or just about to animate return false; } mState = State.ABOUT_TO_ANIMATE; mIsShrinking = mContent.getVisibility() == VISIBLE; if (!mIsShrinking) { // this could make flicker so we test mState in dispatchDraw() // to see if is equal to ABOUT_TO_ANIMATE mContent.setVisibility(VISIBLE); } return true; } @Override public boolean onDown(MotionEvent e) { scrollX = scrollY = 0; lastRawX = curRawX = lastRawY = curRawY = -1; lastEventTime = curEventTime = -1; initChange(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mState = State.FLYING; float velocityX2, velocityY2; if (lastRawX == -1 && lastRawY == -1) // 见onScroll方法 { velocityX2 = (curRawX - e1.getRawX()) / (curEventTime - e1.getEventTime()) * 1000; // px/s velocityY2 = (curRawY - e1.getRawY()) / (curEventTime - e1.getEventTime()) * 1000; } else { velocityX2 = (curRawX - lastRawX) / (curEventTime - lastEventTime) * 1000; velocityY2 = (curRawY - lastRawY) / (curEventTime - lastEventTime) * 1000; } mVelocity = mOrientation == VERTICAL ? velocityY2 : velocityX2; if (Math.abs(mVelocity) > 50) { if (mVelocity > 0) { mAnimatedAcceleration = mMaximumAcceleration; } else { mAnimatedAcceleration = -mMaximumAcceleration; } long now = SystemClock.uptimeMillis(); mAnimationLastTime = now; mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION; mAnimating = true; mHandler.removeMessages(MSG_ANIMATE); mHandler.removeMessages(MSG_PREPARE_ANIMATE); mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime); return true; } return false; } @Override public void onLongPress(MotionEvent e) { // not used } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mState = State.TRACKING; float tmpY = 0, tmpX = 0; if (mOrientation == VERTICAL) { scrollY -= distanceY; if (mPosition == TOP) { tmpY = ensureRange(scrollY, -mContentHeight, 0); } else { tmpY = ensureRange(scrollY, 0, mContentHeight); } } else { scrollX -= distanceX; if (mPosition == LEFT) { tmpX = ensureRange(scrollX, -mContentWidth, 0); } else { tmpX = ensureRange(scrollX, 0, mContentWidth); } } if (tmpX != mTrackX || tmpY != mTrackY) { mTrackX = tmpX; mTrackY = tmpY; // invalidate(); //放在此导致极快速滑动至touch区域外界面不刷新(mTrackX、tmpX均为0) } invalidate(); lastRawX = curRawX; lastRawY = curRawY; lastEventTime = curEventTime; curRawX = e2.getRawX(); curRawY = e2.getRawY(); curEventTime = e2.getEventTime(); return true; } @Override public void onShowPress(MotionEvent e) { // not used } @Override public boolean onSingleTapUp(MotionEvent e) { // not used return false; } } private class SlidingHandler extends Handler { private void doAnimation() { if (mAnimating) { long now = SystemClock.uptimeMillis(); float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s final float v = mVelocity; // px/s final float a = mAnimatedAcceleration; // px/s/s mVelocity = v + (a * t); // px/s mAnimationLastTime = now; switch (mPosition) { case LEFT: mTrackX = mTrackX + (v * t) + (0.5f * a * t * t); // px if (mTrackX > 0) { mTrackX = 0; mState = State.READY; mAnimating = false; mIsShrinking = false; } else if (mTrackX < -mContentWidth) { mTrackX = -mContentWidth; mContent.setVisibility(GONE); mState = State.READY; mAnimating = false; mIsShrinking = true; } break; case RIGHT: mTrackX = mTrackX + (v * t) + (0.5f * a * t * t); if (mTrackX < 0) { mTrackX = 0; mState = State.READY; mAnimating = false; } else if (mTrackX > mContentWidth) { mTrackX = mContentWidth; mContent.setVisibility(GONE); mState = State.READY; mAnimating = false; } break; case TOP: mTrackY = mTrackY + (v * t) + (0.5f * a * t * t); if (mTrackY > 0) { mTrackY = 0; mState = State.READY; mAnimating = false; } else if (mTrackY < -mContentHeight) { mTrackY = -mContentHeight; mContent.setVisibility(GONE); mState = State.READY; mAnimating = false; } break; case BOTTOM: mTrackY = mTrackY + (v * t) + (0.5f * a * t * t); if (mTrackY < 0) { mTrackY = 0; mState = State.READY; mAnimating = false; } else if (mTrackY > mContentHeight) { mTrackY = mContentHeight; mContent.setVisibility(GONE); mState = State.READY; mAnimating = false; } break; } invalidate(); if (!mAnimating) { postProcess(); return; } mCurrentAnimationTime += ANIMATION_FRAME_DURATION; mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime); } } @Override public void handleMessage(Message m) { switch (m.what) { case MSG_ANIMATE: doAnimation(); break; case MSG_PREPARE_ANIMATE: prepareAnimation(); doAnimation(); break; } } private void prepareAnimation() { switch (mPosition) { case LEFT: if (mIsShrinking) { mVelocity = -mMaximumMajorVelocity; mAnimatedAcceleration = -mMaximumAcceleration; } else { mVelocity = mMaximumMajorVelocity; mAnimatedAcceleration = mMaximumAcceleration; if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE) { mTrackX = -mContentWidth; } } break; case RIGHT: if (mIsShrinking) { mVelocity = mMaximumMajorVelocity; mAnimatedAcceleration = mMaximumAcceleration; } else { mVelocity = -mMaximumMajorVelocity; mAnimatedAcceleration = -mMaximumAcceleration; if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE) { mTrackX = mContentWidth; } } break; case TOP: if (mIsShrinking) { mVelocity = -mMaximumMajorVelocity; mAnimatedAcceleration = -mMaximumAcceleration; } else { mVelocity = mMaximumMajorVelocity; mAnimatedAcceleration = mMaximumAcceleration; if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE) { mTrackY = -mContentHeight; } } break; case BOTTOM: if (mIsShrinking) { mVelocity = mMaximumMajorVelocity; mAnimatedAcceleration = mMaximumAcceleration; } else { mVelocity = -mMaximumMajorVelocity; mAnimatedAcceleration = -mMaximumAcceleration; if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE) { mTrackY = mContentHeight; } } break; } if (mState == State.TRACKING) { if (mIsShrinking) { if ((mOrientation == VERTICAL && Math.abs(mTrackY) < mContentHeight / 2) || (mOrientation == HORIZONTAL && Math.abs(mTrackX) < mContentWidth / 2)) { mVelocity = -mVelocity; mAnimatedAcceleration = -mAnimatedAcceleration; mIsShrinking = !mIsShrinking; } } else { if ((mOrientation == VERTICAL && Math.abs(mTrackY) > mContentHeight / 2) || (mOrientation == HORIZONTAL && Math.abs(mTrackX) > mContentWidth / 2)) { mVelocity = -mVelocity; mAnimatedAcceleration = -mAnimatedAcceleration; mIsShrinking = !mIsShrinking; } } } if (mState != State.FLYING && mState != State.TRACKING) { mState = State.CLICK; } } }; private enum State { ABOUT_TO_ANIMATE, ANIMATING, READY, TRACKING, FLYING, CLICK } private static final String TAG = "Panel"; private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f; private static final float MAXIMUM_ACCELERATION = 2000.0f; private static final int MSG_ANIMATE = 1000; private static final int MSG_PREPARE_ANIMATE = 2000; private static final int ANIMATION_FRAME_DURATION = 1000 / 60; public static final int TOP = 0; public static final int BOTTOM = 1; public static final int LEFT = 2; public static final int RIGHT = 3; private OnPanelListener panelListener; private final Handler mHandler = new SlidingHandler(); private float mAnimatedAcceleration; private long mAnimationLastTime; private long mCurrentAnimationTime; private boolean mAnimating; private final int mMaximumMajorVelocity; private final int mMaximumAcceleration; private float lastRawX, lastRawY, curRawX, curRawY; private float lastEventTime, curEventTime; private boolean mIsShrinking; private int mPosition; @SuppressWarnings("unused") private int mDuration; @SuppressWarnings("unused") private boolean mLinearFlying; private int mHandleId; private int mContentId; private View mHandle; private View mContent; private Drawable mOpenedHandle; private Drawable mClosedHandle; private float mTrackX; private float mTrackY; private float mVelocity; private State mState; @SuppressWarnings("unused") private Interpolator mInterpolator; private GestureDetector mGestureDetector; private int mContentHeight; private int mContentWidth; private int mOrientation; private float mWeight; private PanelOnGestureListener mGestureListener; private boolean mBringToFront; OnTouchListener touchListener = new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (mAnimating) { // we are animating return true;// 动画中不响应onTouch事件 } int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { if (mBringToFront) { bringToFront(); } } if (!mGestureDetector.onTouchEvent(event)) { if (action == MotionEvent.ACTION_UP) { // tup up after scrolling long now = SystemClock.uptimeMillis(); mAnimationLastTime = now; mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION; mAnimating = true; mHandler.removeMessages(MSG_ANIMATE); mHandler.removeMessages(MSG_PREPARE_ANIMATE); mHandler.sendMessageAtTime( mHandler.obtainMessage(MSG_PREPARE_ANIMATE), mCurrentAnimationTime); } } return false; } }; @SuppressWarnings("deprecation") public Panel(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel); // duration defaults to 750 ms mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); // position defaults to BOTTOM mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); // linear Flying defaults to false mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); // weight defaults to 0.0 mWeight = a.getFraction(R.styleable.Panel_weight, 0, 1, 0.0f); if (mWeight < 0 || mWeight > 1) { mWeight = 0.0f; Log.w(TAG, a.getPositionDescription() + ": weight must be > 0 and <= 1"); } mOpenedHandle = a.getDrawable(R.styleable.Panel_openedHandle); mClosedHandle = a.getDrawable(R.styleable.Panel_closedHandle); RuntimeException e = null; mHandleId = a.getResourceId(R.styleable.Panel_handle, 0); if (mHandleId == 0) { e = new IllegalArgumentException( a.getPositionDescription() + ": The handle attribute is required and must refer to a valid child."); } mContentId = a.getResourceId(R.styleable.Panel_content, 0); if (mContentId == 0) { e = new IllegalArgumentException( a.getPositionDescription() + ": The content attribute is required and must refer to a valid child."); } a.recycle(); final float density = getResources().getDisplayMetrics().density; mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f); mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f); if (e != null) { throw e; } mOrientation = (mPosition == TOP || mPosition == BOTTOM) ? VERTICAL : HORIZONTAL; setOrientation(mOrientation); mState = State.READY; mGestureListener = new PanelOnGestureListener(); mGestureDetector = new GestureDetector(mGestureListener); mGestureDetector.setIsLongpressEnabled(false); // i DON'T really know why i need this... setBaselineAligned(false); } /** * 绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现, 绘制自己的孩子通过dispatchDraw(canvas)实现 */ @Override protected void dispatchDraw(Canvas canvas) { // String name = getResources().getResourceEntryName(getId()); // Log.d(TAG, name + " ispatchDraw " + mState); // this is why 'mState' was added: // avoid flicker before animation start if (mState == State.ABOUT_TO_ANIMATE && !mIsShrinking) { int delta = mOrientation == VERTICAL ? mContentHeight : mContentWidth; if (mPosition == LEFT || mPosition == TOP) { delta = -delta; } if (mOrientation == VERTICAL) { canvas.translate(0, delta); } else { canvas.translate(delta, 0); } } if (mState == State.TRACKING || mState == State.FLYING || mState == State.CLICK) { canvas.translate(mTrackX, mTrackY); } super.dispatchDraw(canvas); } /** * Gets Panel's mContent * * @return Panel's mContent */ public View getContent() { return mContent; } /** * Gets Panel's mHandle * * @return Panel's mHandle */ public View getHandle() { return mHandle; } /** * Returns the opened status for Panel. * * @return True if Panel is opened, false otherwise. */ public boolean isOpen() { return mContent.getVisibility() == VISIBLE; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); ViewParent parent = getParent(); if (parent != null && parent instanceof FrameLayout) { mBringToFront = true; } } /** * 回调函数 界面初始化快结束时调用 用于得到 mHandle/mContent */ @Override protected void onFinishInflate() { super.onFinishInflate(); mHandle = findViewById(mHandleId); if (mHandle == null) { String name = getResources().getResourceEntryName(mHandleId); throw new RuntimeException( "Your Panel must have a child View whose id attribute is 'R.id." + name + "'"); } mHandle.setClickable(true); mHandle.setOnTouchListener(touchListener); // mHandle.setOnClickListener(clickListener); mContent = findViewById(mContentId); if (mContent == null) { String name = getResources().getResourceEntryName(mHandleId); throw new RuntimeException( "Your Panel must have a child View whose id attribute is 'R.id." + name + "'"); } // reposition children removeView(mHandle); removeView(mContent); if (mPosition == TOP || mPosition == LEFT) { addView(mContent); addView(mHandle); } else { addView(mHandle); addView(mContent); } if (mOpenedHandle != null) { mHandle.setBackground(mOpenedHandle); } mContent.setClickable(true); if (mWeight > 0) { ViewGroup.LayoutParams params = mContent.getLayoutParams(); if (mOrientation == VERTICAL) { params.height = ViewGroup.LayoutParams.MATCH_PARENT; } else { params.width = ViewGroup.LayoutParams.MATCH_PARENT; } mContent.setLayoutParams(params); } } /** * 回调函数 此时其内所有子View 宽度/高度 都已确定 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mContentWidth = mContent.getWidth(); mContentHeight = mContent.getHeight(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mWeight > 0 && mContent.getVisibility() == VISIBLE) { View parent = (View) getParent(); if (parent != null) { if (mOrientation == VERTICAL) { heightMeasureSpec = MeasureSpec.makeMeasureSpec( (int) (parent.getHeight() * mWeight), MeasureSpec.EXACTLY); } else { widthMeasureSpec = MeasureSpec.makeMeasureSpec( (int) (parent.getWidth() * mWeight), MeasureSpec.EXACTLY); } } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 善后工作 比如:改变mHandle背景图 通知开合监听器 */ private void postProcess() { if (mIsShrinking && mClosedHandle != null) { mHandle.setBackground(mClosedHandle); } else if (!mIsShrinking && mOpenedHandle != null) { mHandle.setBackground(mOpenedHandle); } // invoke listener if any if (panelListener != null) { if (mIsShrinking) { panelListener.onPanelClosed(Panel.this); } else { panelListener.onPanelOpened(Panel.this); } } } /** * Sets the acceleration curve for panel's animation. * * @param i * The interpolator which defines the acceleration curve */ public void setInterpolator(Interpolator i) { mInterpolator = i; } /** * Sets the listener that receives a notification when the panel becomes * open/close. * * @param onPanelListener * The listener to be notified when the panel is opened/closed. */ public void setOnPanelListener(OnPanelListener onPanelListener) { panelListener = onPanelListener; } /** * Set the opened state of Panel. * * @param open * True if Panel is to be opened, false if Panel is to be closed. * @param animate * True if use animation, false otherwise. * * @return True if operation was performed, false otherwise. * */ public boolean setOpen(boolean open, boolean animate) { if (mState == State.READY && isOpen() ^ open) { mIsShrinking = !open; if (animate) { mState = State.ABOUT_TO_ANIMATE; if (!mIsShrinking) { // this could make flicker so we test mState in // dispatchDraw() // to see if is equal to ABOUT_TO_ANIMATE mContent.setVisibility(VISIBLE); } long now = SystemClock.uptimeMillis(); mAnimationLastTime = now; mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION; mAnimating = true; mHandler.removeMessages(MSG_ANIMATE); mHandler.removeMessages(MSG_PREPARE_ANIMATE); mHandler.sendMessageAtTime( mHandler.obtainMessage(MSG_PREPARE_ANIMATE), mCurrentAnimationTime); } else { mContent.setVisibility(open ? VISIBLE : GONE); postProcess(); } return true; } return false; } }
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:panel="http://schemas.android.com/apk/res/com.pepper.panel" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <com.pepper.panel.Panel android:id="@+id/panel" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_gravity="left" panel:closedHandle="@drawable/foot_bar_right" panel:content="@+id/panelContent" panel:handle="@+id/panelHandle" panel:openedHandle="@drawable/foot_bar_left" panel:position="left" > <Button android:id="@+id/panelHandle" android:layout_width="33dip" android:layout_height="wrap_content" /> <HorizontalScrollView android:id="@+id/panelContent" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="#556699" android:fillViewport="true" android:scrollbars="none"> <LinearLayout android:id="@+id/panelContentLl" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/foot_bar" android:gravity="center" android:orientation="horizontal" > </LinearLayout> </HorizontalScrollView> </com.pepper.panel.Panel> </RelativeLayout>