`

抽屉 Panel 研究

阅读更多

大家对抽屉控件的第一反应就是系统提供的 如下:

 

 

 

 

其实 该控件的原理说白了 很简单 即:

* ViewGroup 如:LinearLayout 用于放置各种View

* Button 用于 展开/收起 ViewGroup

 

所以该控件的大致布局应如下:

 

 

 

为了降低开发难度 我打算 定义 Panel extends LinearLayout

 

 

[代码 步骤]

 

1. 定义一些XML用到的属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Panel">
         //动画演变时长
        <attr name="animationDuration" format="integer" />
        //摆放位置 只能取下面的4个值
        <attr name="position">
            <enum name="top" value="0" />
            <enum name="bottom" value="1" />
            <enum name="left" value="2" />
            <enum name="right" value="3" />
        </attr>
        //开合是否有动画效果
        <attr name="animationEnable" format="boolean" />
    </declare-styleable>

</resources>

 

 

2. 一个标准的XML为:

<org.panel.Panel
			android:id="@+id/leftPanel" 
		    android:layout_width="wrap_content" 
		    android:layout_height="wrap_content" 
		    panel:position="left"
		    panel:animationDuration="10"
		    panel:animationEnable="true"
		    android:layout_gravity="left"
		>
			<Button
			    android:layout_width="wrap_content" 
			    android:layout_height="wrap_content" 
			/>
			<LinearLayout
			    android:orientation="vertical"
	    		android:layout_width="wrap_content"
	    		android:layout_height="wrap_content"
			>
				<CheckBox
				    android:layout_width="fill_parent" 
				    android:layout_height="wrap_content" 
				    android:text="Top Panel!"
				    android:textSize="16dip"
				    android:textColor="#eee"
				    android:textStyle="bold"
				/>
				<EditText
				    android:layout_width="200dip" 
				    android:layout_height="wrap_content" 
				/>
				<Button
				    android:layout_width="100dp" 
				    android:layout_height="wrap_content" 
				    android:text="OK!"
				/>
			</LinearLayout>
		</org.panel.Panel>

 

 

3. 解析该XML 并设置之

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);
		mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM);
		isAnimation = a.getBoolean(R.styleable.Panel_animationEnable, true);
		a.recycle();
		
		//根据mPosition 决定LinearLayout的android:orientation属性
		mOrientation = (mPosition == TOP || mPosition == BOTTOM)? VERTICAL : HORIZONTAL;
		setOrientation(mOrientation);
		
		//初始化mHandle 背景图
		initialHandlerBg();
		
	}

 

 

4. 设定Button 背景图

//设置mHandle所用背景图    
    private void initialHandlerBg(){
    	if(mPosition == TOP){
    		mOpenedHandle = getResources().getDrawable(R.drawable.top_switcher_expanded_background);
    		mClosedHandle = getResources().getDrawable(R.drawable.top_switcher_collapsed_background);
    	
    	}
    	else if(mPosition == BOTTOM) {
    		mOpenedHandle = getResources().getDrawable(R.drawable.bottom_switcher_expanded_background);
    		mClosedHandle = getResources().getDrawable(R.drawable.bottom_switcher_collapsed_background);
    	
    	}
    	else if(mPosition == LEFT) {
    		mOpenedHandle = getResources().getDrawable(R.drawable.left_switcher_expanded_background);
    		mClosedHandle = getResources().getDrawable(R.drawable.left_switcher_collapsed_background);
    	
    	}
    	else if(mPosition == RIGHT) {
    		mOpenedHandle = getResources().getDrawable(R.drawable.right_switcher_expanded_background);
    		mClosedHandle = getResources().getDrawable(R.drawable.right_switcher_collapsed_background);
    	
    	}
    }

 

 

5. 取出其中的 ViewGroup & Button

//回调函数 界面初始化快结束时调用 用于得到 mHandle/mContent
	protected void onFinishInflate() {
		super.onFinishInflate();
		
		//得到mHandle实例
		mHandle = this.getChildAt(0);
		
		if (mHandle == null) {
            throw new RuntimeException("Your Panel must have a View - mHandle");
		}
		
		mHandle.setOnClickListener(clickListener);
		
		//得到mContent实例
		mContent = this.getChildAt(1);
		if (mContent == null) {
            throw new RuntimeException("Your Panel must have a View - mContent");
		}

		
		//先移除mHandle/mContent 然后根据position决定二者的添加次序
		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
		mContent.setVisibility(GONE);
	}

 

6. 得到ViewGroup 宽度/高度 以决定动画演变范围

   注意其位置 并非放在开始地方 因为那时候返回值都是0

 

@Override //回调函数 此时其内所有子View 宽度/高度 都已确定
	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();
		
		paddingTop = this.getPaddingTop();
		paddingLeft = this.getPaddingLeft();
	}

 

 

7.  定义Button 响应事情 执行 开合ViewGroup

// 定义mHandle监听器 用于开合mContent
	OnClickListener clickListener = new OnClickListener(){
		public void onClick(View v) {
				// TODO Auto-generated method stub
			if(!isContentExpand){
				open();
			}
			else {
				close();
			}
			
			//置反 即:开-合-开-合-开-...
			isContentExpand = !isContentExpand;
		}
	};

 

 

8. 定义开合的回调函数 具体效果 见:setOnClickListener(OnClickListener listener)

//回调函数 用于监听 Panel 的开合 效果见:setOnClickLstener(OnClickListener listener)
	public static interface OnPanelListener {
		//- open
		public void onPanelOpened(Panel panel);
		
		//- close
		public void onPanelClosed(Panel panel);
    }

 

 

10. 开 即: 打开ViewGroup

public void open(){
		if(isAnimation){
			doAnimationOpen();
		}
		else {
			doOpen();
		}
		
	}
	public void doOpen(){
		mContent.setVisibility(VISIBLE);
		
	}
	
	public void doAnimationOpen(){
		mContent.setVisibility(VISIBLE);
		post(aOpen);
	}

 

11. 定义开的Animation

//- open
	Runnable aOpen = new Runnable() {
		public void run() {
			TranslateAnimation animation;
			int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;
			int calculatedDuration = 0;
			
			if(mPosition == TOP){
				fromYDelta = -1 * mContentHeight;
				toXDelta = 0;
				
				calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight;
			}
			else if(mPosition == BOTTOM){
				fromYDelta = paddingTop;
				toYDelta = fromYDelta + 1 * mContentHeight;
				
				calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight;
			}
			else if(mPosition == LEFT){
				fromXDelta = -1 * mContentWidth;
				toXDelta = 0;
				
				calculatedDuration = mDuration * Math.abs(toXDelta - fromXDelta) / mContentWidth;
			}
			else if(mPosition == RIGHT){
				fromXDelta = paddingLeft;
				toXDelta = fromYDelta + 1 * mContentHeight;
				
				calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight;
			}
			
			animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
			animation.setDuration(calculatedDuration);
			animation.setAnimationListener(aOListener);

			startAnimation(animation);
		}
	};

 

 

12. 合 即:关闭ViewGroup

public void close(){
		if(isAnimation){
			doAnimationClose();
		}
		else {
			doClose();
		}
	}
	public void doClose(){
		mContent.setVisibility(GONE);
		
	}
	public void doAnimationClose(){
		post(aClose);
		
	}

 

13. 定义合的Animation

//- close
	Runnable aClose = new Runnable() {
		public void run() {
			TranslateAnimation animation;
			int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;
			int calculatedDuration = 0;
			
			if(mPosition == TOP){
				toYDelta = -1 * mContentHeight;
				
				calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight;
			}
			else if(mPosition == BOTTOM){
				fromYDelta = 1 *  mContentHeight;
				toYDelta = paddingTop;
				
				calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight;
			}
			else if(mPosition == LEFT){
				toXDelta = -1 * mContentWidth;
				
				calculatedDuration = mDuration * Math.abs(toXDelta - fromXDelta) / mContentWidth;
			}
			else if(mPosition == RIGHT){
				
			}
			
			animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
			animation.setDuration(calculatedDuration);
			animation.setAnimationListener(aCListener);
			
			startAnimation(animation);
		}
	};

 

 

14. 定义二者Animation 的回调函数 即:结束后 更改Button背景图 通知OnPanelListener

//善后工作 比如:改变mHandle背景图 通知开合监听器
	private void postProcess() {
		// to update mHandle 's background 
		if (!isContentExpand ) {
			mHandle.setBackgroundDrawable(mClosedHandle);
		} 
		else {
			mHandle.setBackgroundDrawable(mOpenedHandle);
		}
		
		// invoke listener if any
		if (panelListener != null) {
			if (isContentExpand) {
				panelListener.onPanelOpened(Panel.this);
			}
			else {
				panelListener.onPanelClosed(Panel.this);
			}
		}
	}

 

 

15. emulator 运行截图:

* 先贴其布局

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:panel="http://schemas.android.com/apk/res/org.panel"
    android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:orientation="vertical"
>

		<org.panel.Panel
			android:id="@+id/leftPanel" 
		    android:layout_width="wrap_content" 
		    android:layout_height="wrap_content" 
		    panel:position="left"
		    panel:animationDuration="10"
		    panel:animationEnable="true"
		    android:layout_gravity="left"
		>
			<Button
			    android:layout_width="wrap_content" 
			    android:layout_height="wrap_content" 
			/>
			<LinearLayout
			    android:orientation="vertical"
	    		android:layout_width="wrap_content"
	    		android:layout_height="wrap_content"
			>
				<CheckBox
				    android:layout_width="fill_parent" 
				    android:layout_height="wrap_content" 
				    android:text="Top Panel!"
				    android:textSize="16dip"
				    android:textColor="#eee"
				    android:textStyle="bold"
				/>
				<EditText
				    android:layout_width="200dip" 
				    android:layout_height="wrap_content" 
				/>
				<Button
				    android:layout_width="100dp" 
				    android:layout_height="wrap_content" 
				    android:text="OK!"
				/>
			</LinearLayout>
		</org.panel.Panel>
		
		<org.panel.Panel
			android:id="@+id/rightPanel" 
		    android:layout_width="wrap_content" 
		    android:layout_height="wrap_content" 
		    panel:position="right"
		    panel:animationDuration="10"
		    panel:animationEnable="true"
		    android:layout_gravity="right"
		>
			<Button
			    android:layout_width="wrap_content" 
			    android:layout_height="wrap_content" 
			/>
			<LinearLayout
			    android:orientation="vertical"
	    		android:layout_width="wrap_content"
	    		android:layout_height="wrap_content"
			>
				<ImageView
				    android:layout_width="wrap_content" 
				    android:layout_height="wrap_content" 
				    android:src="@drawable/beijing4_b"
				/>
			</LinearLayout>
		</org.panel.Panel>
		
		<LinearLayout
		    android:layout_width="fill_parent" 
		    android:layout_height="wrap_content" 
		    android:orientation="vertical" 
		    >
			<TextView
			    android:layout_width="fill_parent" 
			    android:layout_height="wrap_content" 
			    android:textSize="16dip"
			    android:textColor="#ddd"
			    android:text="other area!!!!!!!!"
			/>
			<Button
					android:id="@+id/button"
				    android:layout_width="100dp" 
				    android:layout_height="wrap_content" 
				    android:text="Yes!"
				/>
		</LinearLayout>
		
		
</LinearLayout>

 

 

* 运行截图:

 - 开

 

- 合

 

 

完工!


分享到:
评论

相关推荐

    自定义Android滑动抽屉Panel.zip

    在Android应用开发中,滑动抽屉Panel是一种常见的设计模式,它允许用户通过从屏幕边缘向内滑动来显示或隐藏附加内容。本资源“自定义Android滑动抽屉Panel.zip”提供了一个自定义实现这一功能的示例,包含源码和相关...

    实例15 实现QQ程序的抽屉效果.rar_QQ界面_c# 抽屉_抽屉

    在C#中,我们可以利用控件(如Panel或Form)的移动和大小调整功能来实现抽屉效果。以下是一些关键步骤: 1. 创建主窗体:首先,创建一个主窗体作为QQ界面的基础,设置其大小和布局。你可以使用Visual Studio的窗体...

    “可动态布局”的Android抽屉组件之构建基础

    在Android应用开发中,抽屉组件(Drawer Layout)是一种常见的...ExPanel(1)作为一个示例,为我们提供了一种可能的实现方式,通过研究其源码,我们可以学习到更多的技巧和最佳实践,进一步提升我们的Android开发能力。

    包含多种特效的demo,实现多种动画效果,抽屉效果、多种自定义的view、还有输入法.zip

    例如,`Switcher.class`可能表示一个自定义的切换视图类,`Panel.class`可能是实现抽屉效果的类,而`TestInterpolators$2.class`可能是一个测试不同动画插值器效果的辅助类。 综上所述,这个压缩包是一个全面的...

    core-drawer-panel:简单的两部分响应面板

    "core-drawer-panel"是基于Polymer 0.5框架的一个...对于想要学习或重温这个组件的开发者,可以从名为"core-drawer-panel-master"的压缩包文件中获取源代码,研究其工作原理,但应意识到这只是一个历史性的参考资料。

    DrawerMenu.rar

    在这里,开发者可能定义了一个隐藏的Panel或者UserControl作为菜单容器,然后在用户触发特定事件(如点击按钮)时,通过调整其位置和宽度来模拟抽屉滑出的效果。这通常涉及到对控件的Size、Location属性的动态修改,...

    Android各种特效源码

    例如,一个常见的自定义Panel是底部导航抽屉,用户可以从中选择不同的功能模块。 2. 空键(Empty View): 空键是当数据为空时展示的一种特殊视图,它向用户传达了当前无数据可用的信息。在设计用户界面时,为了...

    AndroidMiscWidgets

    项目中可能包含的Widgets有:滑动面板(SlidingPanel)、折叠面板(CollapsiblePanel)、抽屉面板(DrawerPanel)等。这些Widgets通常具有自定义布局、动画效果和触摸交互特性,使得开发者可以轻松地定制符合应用...

    Jquery Mobile 簡單應用及源碼

    侧窗是jQuery Mobile中的一种布局元素,常用于实现类似抽屉的效果,可以隐藏或显示附加信息。创建侧窗需要通过HTML5的`&lt;div&gt;`标签,并设置相应的类名,如`&lt;div data-role="panel" id="myPanel"&gt;`。通过JavaScript...

    QSidePanel:Qt5侧面板

    在Qt5框架中,`QSidePanel`是一个自定义控件,它被设计用来作为界面中的侧边栏或抽屉式面板。这个组件通常用于显示附加信息、菜单选项或者工具栏,可以方便地进行展开和收起,为用户提供更加灵活的交互体验。`...

    自定义控件demo

    3. **侧滑面板(Swipeable Panel)**:侧滑面板通常用于实现类似抽屉式的效果,比如谷歌地图中的侧滑导航栏。可以使用CoordinatorLayout结合Behavior来实现,也可以使用DrawerLayout。主要关注滑动手势的识别和面板...

    安卓SlidingMenu各种菜单侧滑菜单相关-史上最牛最完美的slidingMenu沉浸式源代码.rar

    1. **Android Sliding Up Panel**:这是一个可滑动的顶部和底部面板库,可以用于创建类似Google Play音乐应用的侧滑效果。 2. **Android DrawerLayout**:这是Android SDK中内置的一个组件,用于创建抽屉式导航菜单...

    swift-FAPanels-Swift

    这个项目主要是为 iOS 应用程序提供一种灵活的、可自定义的面板(Panel)展示方式。在 iOS 开发中,面板通常用于创建滑出式菜单、模态视图或者浮动操作按钮等交互元素,为用户界面增加丰富的动态效果。 【描述】...

    sliding-pane-layout:SlidingPaneLayout 部分可见,带有淡入淡出

    向右滑动,则会隐藏,通常用于实现类似抽屉式的导航菜单。 要实现部分可见和淡入淡出效果,开发者需要对SlidingPaneLayout的一些关键属性进行调整。例如,可以设置`overhangSize`属性,使得滑动视图在完全滑出前...

    Android Design官方设计文档翻译 pdf

    这部分内容讨论了不同的导航模式,如抽屉式导航栏、底部导航栏等,并给出了具体的实现建议。 #### 二十、ActionBar - 动作栏 动作栏是Android应用中非常重要的组成部分。这部分内容介绍了如何设计和实现动作栏,使...

Global site tag (gtag.js) - Google Analytics