SlidingDrawerDemo.java:
package org.lee.android; import org.lee.android.ExpoInterpolator.EasingType; import org.lee.android.ExpoInterpolator.ExpoInterpolator; import org.lee.android.widget.Panel; import org.lee.android.widget.Panel.OnPanelListener; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class SlidingDrawerDemo extends Activity implements OnPanelListener{ @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); super.setContentView(R.layout.sildingdrawer); Panel panel; panel = (Panel) findViewById(R.id.topPanel); panel.setOnPanelListener(this); panel.setInterpolator(new ExpoInterpolator(EasingType.OUT)); } public void onPanelClosed(Panel panel) { String panelName = getResources().getResourceEntryName(panel.getId()); Log.d("TestPanels", "Panel [" + panelName + "] closed"); } public void onPanelOpened(Panel panel) { String panelName = getResources().getResourceEntryName(panel.getId()); Log.d("TestPanels", "Panel [" + panelName + "] opened"); } }
sildingdrawer.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:panel="http://schemas.android.com/apk/res/org.lee.android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ffffff" android:orientation="vertical" > <RelativeLayout android:id="@+id/listHeader" android:layout_width="fill_parent" android:layout_height="48dp" android:layout_alignParentTop="true" android:background="#F7BAD6" > <ImageView android:id="@+id/header_back" android:layout_width="12dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_marginLeft="12px" android:background="@drawable/header_back" /> <TextView android:id="@+id/shop_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="15px" android:layout_toRightOf="@id/header_back" android:text="标题" android:textColor="#ffffff" android:textSize="28px" /> <ImageView android:id="@+id/three_rect" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="12dp" android:background="@drawable/three_rect" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/listHeader" android:orientation="vertical" > <!-- tab标签栏 --> <LinearLayout android:id="@+id/header" android:layout_width="fill_parent" android:layout_height="40dp" android:background="#FFFFFF" > <TextView android:id="@+id/text1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="页卡1" android:textColor="#000000" android:textSize="22.0dip" /> <View android:layout_width="2dip" android:layout_height="30dip" android:layout_gravity="center_vertical" android:background="#D7D7D7" /> <TextView android:id="@+id/text2" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="页卡2" android:textColor="#000000" android:textSize="22.0dip" /> <View android:layout_width="2dip" android:layout_height="30dip" android:layout_gravity="center_vertical" android:background="#D7D7D7" /> <TextView android:id="@+id/text3" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="页卡3" android:textColor="#000000" android:textSize="22.0dip" /> <View android:layout_width="2dip" android:layout_height="30dip" android:layout_gravity="center_vertical" android:background="#D7D7D7" /> <TextView android:id="@+id/text4" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1.0" android:gravity="center" android:text="页卡4" android:textColor="#000000" android:textSize="22.0dip" /> <!-- 最右侧分类标识 --> <FrameLayout android:layout_width="50dp" android:layout_height="fill_parent" android:layout_marginRight="5dp" > </FrameLayout> </LinearLayout> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@id/header" android:background="#ff6600" android:text="和哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈和" android:textColor="#ffffff" android:textSize="18sp" android:textStyle="bold" /> <org.lee.android.widget.Panel android:id="@+id/topPanel" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="top|right" android:paddingBottom="4dip" panel:animationDuration="1200" panel:content="@+id/panelContent" panel:handle="@+id/panelHandle" panel:linearFlying="true" panel:position="top" > <TextView android:id="@+id/panelHandle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:background="@drawable/up" android:gravity="center" android:padding="5dp" android:text="分类" android:textColor="#ffffff" android:textSize="15sp" /> <LinearLayout android:id="@+id/panelContent" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <CheckBox android:layout_width="fill_parent" android:layout_height="60dip" android:background="#688" android:gravity="center" android:text="top check box" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#323299" android:gravity="center" android:padding="4dip" android:text="Bounce\nInterpolator" android:textColor="#eee" android:textSize="16dip" android:textStyle="bold" /> </LinearLayout> </org.lee.android.widget.Panel> </RelativeLayout> </RelativeLayout>
EasingType.java:
package org.lee.android.ExpoInterpolator; public class EasingType { public static final int IN = 0; public static final int OUT = 1; public static final int INOUT = 2; }
ExpoInterpolator.java:
package org.lee.android.ExpoInterpolator; /* * * Open source under the BSD License. * * Copyright © 2001 Robert Penner * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ import android.view.animation.Interpolator; public class ExpoInterpolator implements Interpolator { private int type; public ExpoInterpolator(int type) { this.type = type; } public float getInterpolation(float t) { if (type == EasingType.IN) { return in(t); } else if (type == EasingType.OUT) { return out(t); } else if (type == EasingType.INOUT) { return inout(t); } return 0; } private float in(float t) { return (float) ((t==0) ? 0 : Math.pow(2, 10 * (t - 1))); } private float out(float t) { return (float) ((t>=1) ? 1 : (-Math.pow(2, -10 * t) + 1)); } private float inout(float t) { if (t == 0) { return 0; } if (t >= 1) { return 1; } t *= 2; if (t < 1) { return (float) (0.5f * Math.pow(2, 10 * (t - 1))); } else { return (float) (0.5f * (-Math.pow(2, -10 * --t) + 2)); } } }
Panel.java:
package org.lee.android.widget; import org.lee.android.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; 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.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.LinearLayout; public class Panel extends LinearLayout { private static final String TAG = "Panel"; /** * 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); } private boolean mIsShrinking; private int mPosition; private int mDuration; 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 OnPanelListener panelListener; 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 enum State { ABOUT_TO_ANIMATE, ANIMATING, READY, TRACKING, FLYING, }; private State mState; private Interpolator mInterpolator; private GestureDetector mGestureDetector; private int mContentHeight; private int mContentWidth; private int mOrientation; private float mWeight; private PanelOnGestureListener mGestureListener; private boolean mBringToFront; public Panel(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel); mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); // duration defaults to 750 ms mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); // position defaults to BOTTOM mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); // linearFlying defaults to false mWeight = a.getFraction(R.styleable.Panel_weight, 0, 1, 0.0f); // weight defaults to 0.0 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(); 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); } /** * 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; } /** * Gets Panel's mHandle * * @return Panel's mHandle */ public View getHandle() { return mHandle; } /** * Gets Panel's mContent * * @return Panel's mContent */ public View getContent() { return mContent; } /** * Sets the acceleration curve for panel's animation. * * @param i The interpolator which defines the acceleration curve */ public void setInterpolator(Interpolator i) { mInterpolator = i; } /** * 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); } post(startAnimation); } else { mContent.setVisibility(open? VISIBLE : GONE); postProcess(); } return true; } return false; } /** * Returns the opened status for Panel. * * @return True if Panel is opened, false otherwise. */ public boolean isOpen() { return mContent.getVisibility() == VISIBLE; } @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.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 (mClosedHandle != null) { mHandle.setBackgroundDrawable(mClosedHandle); } mContent.setClickable(true); mContent.setVisibility(GONE); if (mWeight > 0) { ViewGroup.LayoutParams params = mContent.getLayoutParams(); if (mOrientation == VERTICAL) { params.height = ViewGroup.LayoutParams.FILL_PARENT; } else { params.width = ViewGroup.LayoutParams.FILL_PARENT; } mContent.setLayoutParams(params); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); ViewParent parent = getParent(); if (parent != null && parent instanceof FrameLayout) { mBringToFront = true; } } @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); } @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 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) { canvas.translate(mTrackX, mTrackY); } super.dispatchDraw(canvas); } private float ensureRange(float v, int min, int max) { v = Math.max(v, min); v = Math.min(v, max); return v; } OnTouchListener touchListener = new OnTouchListener() { float touchX, touchY; public boolean onTouch(View v, MotionEvent event) { if (mState == State.ANIMATING) { // we are animating return false; } int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { if (mBringToFront) { bringToFront(); } touchX = event.getX(); touchY = event.getY(); } if (!mGestureDetector.onTouchEvent(event)) { if (action == MotionEvent.ACTION_UP) { // tup up after scrolling int size = (int) (Math.abs(touchX - event.getX()) + Math .abs(touchY - event.getY())); if (size == mContentWidth || size == mContentHeight) { mState = State.ABOUT_TO_ANIMATE; //Log.e("size", String.valueOf(size)); //Log.e(String.valueOf(mContentWidth),String.valueOf(mContentHeight)); } post(startAnimation); } } return false; } }; OnClickListener clickListener = new OnClickListener() { public void onClick(View v) { if (mBringToFront) { bringToFront(); } if (initChange()) { post(startAnimation); } } }; 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; } Runnable startAnimation = new Runnable() { public void run() { // this is why we post this Runnable couple of lines above: // now its save to use mContent.getHeight() && mContent.getWidth() TranslateAnimation animation; int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0; if (mState == State.FLYING) { mIsShrinking = (mPosition == TOP || mPosition == LEFT) ^ (mVelocity > 0); } int calculatedDuration; if (mOrientation == VERTICAL) { int height = mContentHeight; if (!mIsShrinking) { fromYDelta = mPosition == TOP? -height : height; } else { toYDelta = mPosition == TOP? -height : height; } if (mState == State.TRACKING) { if (Math.abs(mTrackY - fromYDelta) < Math.abs(mTrackY - toYDelta)) { mIsShrinking = !mIsShrinking; toYDelta = fromYDelta; } fromYDelta = (int) mTrackY; } else if (mState == State.FLYING) { fromYDelta = (int) mTrackY; } // for FLYING events we calculate animation duration based on flying velocity // also for very high velocity make sure duration >= 20 ms if (mState == State.FLYING && mLinearFlying) { calculatedDuration = (int) (1000 * Math.abs((toYDelta - fromYDelta) / mVelocity)); calculatedDuration = Math.max(calculatedDuration, 20); } else { calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight; } } else { int width = mContentWidth; if (!mIsShrinking) { fromXDelta = mPosition == LEFT? -width : width; } else { toXDelta = mPosition == LEFT? -width : width; } if (mState == State.TRACKING) { if (Math.abs(mTrackX - fromXDelta) < Math.abs(mTrackX - toXDelta)) { mIsShrinking = !mIsShrinking; toXDelta = fromXDelta; } fromXDelta = (int) mTrackX; } else if (mState == State.FLYING) { fromXDelta = (int) mTrackX; } // for FLYING events we calculate animation duration based on flying velocity // also for very high velocity make sure duration >= 20 ms if (mState == State.FLYING && mLinearFlying) { calculatedDuration = (int) (1000 * Math.abs((toXDelta - fromXDelta) / mVelocity)); calculatedDuration = Math.max(calculatedDuration, 20); } else { calculatedDuration = mDuration * Math.abs(toXDelta - fromXDelta) / mContentWidth; } } mTrackX = mTrackY = 0; if (calculatedDuration == 0) { mState = State.READY; if (mIsShrinking) { mContent.setVisibility(GONE); } postProcess(); return; } animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta); animation.setDuration(calculatedDuration); animation.setAnimationListener(animationListener); if (mState == State.FLYING && mLinearFlying) { animation.setInterpolator(new LinearInterpolator()); } else if (mInterpolator != null) { animation.setInterpolator(mInterpolator); } startAnimation(animation); } }; private AnimationListener animationListener = new AnimationListener() { public void onAnimationEnd(Animation animation) { mState = State.READY; if (mIsShrinking) { mContent.setVisibility(GONE); } postProcess(); } public void onAnimationRepeat(Animation animation) { } public void onAnimationStart(Animation animation) { mState = State.ANIMATING; } }; private void postProcess() { if (mIsShrinking && mClosedHandle != null) { mHandle.setBackgroundDrawable(mClosedHandle); } else if (!mIsShrinking && mOpenedHandle != null) { mHandle.setBackgroundDrawable(mOpenedHandle); } // invoke listener if any if (panelListener != null) { if (mIsShrinking) { panelListener.onPanelClosed(Panel.this); } else { panelListener.onPanelOpened(Panel.this); } } } class PanelOnGestureListener implements OnGestureListener { float scrollY; float scrollX; public void setScroll(int initScrollX, int initScrollY) { scrollX = initScrollX; scrollY = initScrollY; } public boolean onDown(MotionEvent e) { scrollX = scrollY = 0; initChange(); return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mState = State.FLYING; mVelocity = mOrientation == VERTICAL? velocityY : velocityX; post(startAnimation); return true; } public void onLongPress(MotionEvent e) { // not used } 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(); } return true; } public void onShowPress(MotionEvent e) { // not used } public boolean onSingleTapUp(MotionEvent e) { // not used return false; } } }
attrs.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Panel"> <!-- Defines panel animation duration in ms. --> <attr name="animationDuration" format="integer" /> <!-- Defines panel position on the screen. --> <attr name="position"> <!-- Panel placed at top of the screen. --> <enum name="top" value="0" /> <!-- Panel placed at bottom of the screen. --> <enum name="bottom" value="1" /> <!-- Panel placed at left of the screen. --> <enum name="left" value="2" /> <!-- Panel placed at right of the screen. --> <enum name="right" value="3" /> </attr> <!-- Identifier for the child that represents the panel's handle. --> <attr name="handle" format="reference" /> <!-- Identifier for the child that represents the panel's content. --> <attr name="content" format="reference" /> <!-- Defines if flying gesture forces linear interpolator in animation. --> <attr name="linearFlying" format="boolean" /> <!-- Defines size relative to parent (must be in form: nn%p). --> <attr name="weight" format="fraction" /> <!-- Defines opened handle (drawable/color). --> <attr name="openedHandle" format="reference|color" /> <!-- Defines closed handle (drawable/color). --> <attr name="closedHandle" format="reference|color" /> </declare-styleable> </resources>
相关推荐
综上所述,这个项目提供了实现Android上下抽屉效果的方法,涉及到了触摸事件处理、动画效果、视图状态管理和用户体验等多个方面的知识。通过深入研究这个项目,开发者可以更好地理解和掌握如何在Android中创建定制的...
8. **响应式设计**: 考虑到不同设备尺寸和屏幕方向,抽屉效果应具备响应式设计。例如,某些情况下,可能需要在横屏模式下禁用抽屉效果,或者调整抽屉的位置和大小。 9. **Android Design Support Library**: 抽屉...
一个使用ViewDragHelper来实现的 安卓抽屉式滑动效果
通过以上步骤,你可以创建一个具有上下伸缩和展开效果的Android抽屉下拉菜单。这个过程中,不断优化用户体验,让交互更加自然流畅,是提升应用品质的关键。同时,持续学习和分享新的实现方式,也是提升个人技能的...
这种效果在许多应用中都有应用,如Google Maps、Facebook等,而百度地图也采用了类似的抽屉式导航设计。本项目"Android-仿百度地图抽屉拖拽效果"旨在实现一个类似的功能,让用户在自己的Android应用中也能添加这一...
在Android应用开发中,抽屉式导航菜单(Drawer Menu)是一种常见的设计模式,它模仿了Facebook应用...通过学习和理解这个源码,开发者能够加深对Android抽屉式导航菜单实现原理的理解,并能在自己的项目中灵活运用。
这个从gitHub上整理的Android抽屉效果项目,涵盖了左滑、右滑、上滑和下滑等多种交互方式,为开发者提供了实现各种菜单效果的工具。 首先,我们来了解一下抽屉布局的基本概念。抽屉布局(DrawerLayout)是Android ...
总之,实现"android ListView向下展开 抽屉效果"涉及到了自定义Adapter、ViewHolder、点击事件处理、动画应用等多个方面。通过学习和实践,开发者可以创造出更丰富的用户界面,提升Android应用的交互性和吸引力。
在Android开发中,SlidingDrawer是一个非常常见的组件,它提供了抽屉式的滑动效果,常用于隐藏或显示一些辅助操作或菜单。这个组件允许用户通过拖动一个手柄来打开和关闭一个隐藏的视图,使得用户界面更加交互性和...
"安卓动画效果相关-android抽屉效果.rar"这个资源包主要关注的是Android平台上的抽屉(Drawer)效果,这种效果常见于许多应用的侧滑菜单,例如Google Maps或者Facebook应用。抽屉效果通常用于隐藏和显示一个侧边栏,...
在Android应用开发中,抽屉式导航栏(DrawerLayout)是一种常见的设计模式,它允许用户通过从屏幕边缘向内滑动来展示一个可隐藏的菜单,通常用于放置导航选项。这个设计灵感来源于现实生活中的抽屉,因此得名。在本...
在Android系统中,"抽屉式体验"通常指的是应用程序抽屉(App Drawer)的设计,这是一种组织和访问手机上所有应用的界面。不同于iOS系统中所有应用都显示在主屏幕上,Android采用抽屉式布局,将非常用或者不希望在...
在Android应用开发中,"右边抽屉式菜单"是一种常见的设计模式,用于提供导航和设置选项,它通常从屏幕的右侧滑出,给人一种简洁而直观的用户体验。这种设计风格源自Google的Material Design指南,被广泛应用于各种...
在Android应用开发中,抽屉效果(Drawer Effect)是一种常见的设计模式,通常用于实现侧滑菜单,让用户能够从屏幕边缘滑出一个隐藏的面板来访问更多功能或内容。这种效果在许多应用程序中都能看到,例如Google Maps...
通过以上描述,我们可以了解到在Android开发中如何创建一个可从上往下拉的下拉抽屉式控件,以及如何结合PopWindow提供更多的交互功能。在实际应用中,开发者可以根据项目需求对这些基础知识进行扩展和定制。
通过以上这些知识点,你可以构建一个功能完备、用户体验优秀的Android抽屉式菜单。在实际开发中,还可以根据具体需求进行扩展和优化,比如添加手势识别库以支持更多手势操作,或者结合其他组件实现更复杂的功能。...
在Android应用开发中,抽屉效果(Drawer Effect)通常指的是侧滑导航菜单,它允许用户从屏幕边缘滑动以展示或隐藏一个包含导航选项的面板。这种设计模式在许多现代移动应用中非常常见,比如Google Maps和Facebook。...
这个效果使得用户可以通过从屏幕左侧滑动来显示或隐藏一个包含导航选项的面板,模仿了现实生活中抽屉的动作,因此得名"抽屉式导航"。在Android SDK中,我们可以使用`android.support.v4.widget.DrawerLayout`来实现...
在Android应用开发中,自定义滑动菜单,也被称为抽屉式导航(Drawer Navigation)或侧滑菜单,是一种常见的设计模式。它允许用户通过从屏幕边缘向内滑动来显示额外的功能选项或导航层次。这种设计在许多应用程序中都能...