抽屉 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>
* 运行截图:
- 开
- 合
完工!