应该就是传说中的手机人人客户端的实现方法了吧
主要是老外蛋疼的代码 改了一下
需要自定义view
<?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/com.su.houses"//和包名一致!
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFFFF"
android:orientation="horizontal" >
<com.su.houses.panel.Panel
android:id="@+id/leftPanel1"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
panel:closedHandle="@xml/left_switcher_collapsed_background"
panel:openedHandle="@xml/left_switcher_expanded_background"
panel:position="left" >
<Button
android:id="@+id/panelHandle"
android:layout_width="10dip"
android:layout_height="fill_parent" />
<!-- 下面是抽屉的布局 -->
<LinearLayout
android:id="@id/panelContent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#999932"
android:gravity="center"
android:orientation="vertical" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="项目介绍" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="总体规划" />
<Button
android:layout_width= "wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="交通动线"
wrap_content="" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="生活配套" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="未来生活" />
</LinearLayout>
</com.su.houses.panel.Panel>
<ImageView
android:id="@+id/project_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/dogs" />
</LinearLayout>
Panel.java
package com.su.houses.panel;
import com.su.houses.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.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
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.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);
}
private boolean mIsShrinking; // 判断是否是正在收缩状态
private int mPosition;
private int mDuration;
private boolean mLinearFlying;
private View mHandle;
private View mContent;
private Drawable mOpenedHandle;
private Drawable mClosedHandle;
private float mTrackX;// X方向上的隐藏追踪
private float mTrackY;// Y方向上的隐藏跟踪
private float mVelocity;// Fling状态每秒钟划的像素。
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, // 由READY进入这个状态,但还没有发生动画,滚动,和滑动
ANIMATING, // 正在动画
READY, // 就绪状态什么都不做
TRACKING, // 拖动
FLYING,
};
private State mState;
private Interpolator mInterpolator;
private GestureDetector mGestureDetector; // 手势监听器
private int mContentHeight;
private int mContentWidth;
private int mOrientation; // 这个方向是动画出现的方向。可以修改mPositon来修改实验一下
private PanelOnGestureListener mGestureListener; // 手势监听器的回调
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
mOpenedHandle = a.getDrawable(R.styleable.Panel_openedHandle);
mClosedHandle = a.getDrawable(R.styleable.Panel_closedHandle);
a.recycle();
mOrientation = (mPosition == TOP || mPosition == BOTTOM) ? VERTICAL : HORIZONTAL;
setOrientation(mOrientation);
mState = State.READY; // 记录控件的各个状态
mGestureListener = new PanelOnGestureListener();
mGestureDetector = new GestureDetector(mGestureListener);
mGestureDetector.setIsLongpressEnabled(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.
*
*/
public void setOpen(boolean open, boolean animate)
{
if (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();
}
}
}
/**
* Returns the opened status for Panel.
*
* @return True if Panel is opened, false otherwise.
*/
public boolean isOpen()
{
return mContent.getVisibility() == VISIBLE;
}
/*
* 在这里完成了一些handler与Content的初始化工作
*/
@Override
protected void onFinishInflate()
{
super.onFinishInflate(); // 调用超类方法
mHandle = findViewById(R.id.panelHandle); // 获取handle引用设置Handle的监听器
if (mHandle == null)
{
throw new RuntimeException("Your Panel must have a View whose id attribute is 'R.id.panelHandle'");
}
mHandle.setOnTouchListener(touchListener);
mContent = findViewById(R.id.panelContent); // 获取Content的引用
if (mContent == null)
{
throw new RuntimeException("Your Panel must have a View whose id attribute is 'R.id.panelContent'");
}
// reposition children
removeView(mHandle);
removeView(mContent);
if (mPosition == TOP || mPosition == LEFT)
{
addView(mContent);
addView(mHandle);
}
else
{
addView(mHandle);
addView(mContent);
}
if (mClosedHandle != null) // 设置handle的背景
{
mHandle.setBackgroundDrawable(mClosedHandle);
}
mContent.setVisibility(GONE); // 隐藏内容
}
/*
* 主要是获取了Content的宽高
*/
@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());
// 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);// translate(float,float)的作用。将控件的起始坐标平移到(0,delta)的位置
}
else
{
// 这里平移到了delta,0的位置,但是等控件完全展开后如果还是这个矩阵的话,
// 那Content应该是看不到,现在能 看到,那在哪里转化了呢。动画完了就还原
canvas.translate(delta, 0);
}
}
if (mState == State.TRACKING || mState == State.FLYING)
{
canvas.translate(mTrackX, mTrackY);
/*
* 激活状态的Content和handler以mTranceX和mTranckY为参数不断的移动就能形成动画
*/
/*
* 不会,translate只能使view看起来发生了变化,但layout(),不会被调用 也就是,外部的处罚事件还是会按照translate以前的标准触发。
*/
/*
* 没什么关系,translate不会改变框架中事件的判断。
*/
}
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()
{
int initX;
int initY;
boolean setInitialPosition; // 判断是否设置了初始滚动位置 它是类的成员函数
public boolean onTouch(View v, MotionEvent event)
{
int action = event.getAction();
// if 用户向下按
// Content的X方向和Y方向滚动都为0
// if 内容为隐藏状态 根据方向和位置修改X方向或Y方向上的滚动
if (action == MotionEvent.ACTION_DOWN)
{
initX = 0;
initY = 0;
if (mContent.getVisibility() == GONE)
{
// since we may not know content dimensions we use factors here
if (mOrientation == VERTICAL)
{
initY = mPosition == TOP ? -1 : 1;
}
else
{
initX = mPosition == LEFT ? -1 : 1;
}
}
// 设置初始化的记录为true
setInitialPosition = true;
}
else
{
// if 用户按下的不是向下
// if 已经设置了初始滚动位置
// 修改内容X,Y方向上的滚动。
// 设置控件滚动的初始化位置
// 设置初始化的记录为false
/*
* ACTION_DOWN的操作都是根据handle的,Content激活后重新layout,handle的位置发生了改变, 而以后所有的操作的坐标又都是根据现在的handler的位置的,这样就存在了误差
* 所有需要offsetLocation把这个误差修正过来。
*/
if (setInitialPosition)
{
// now we know content dimensions, so we multiply factors...
initX *= mContentWidth;
initY *= mContentHeight;
// ... and set initial panel's position
mGestureListener.setScroll(initX, initY);
setInitialPosition = false;
// for offsetLocation we have to invert values
initX = -initX;
initY = -initY;
}
// offset every ACTION_MOVE & ACTION_UP event
// 如果这里去掉,开始拖动不响应,拖动了一段距离后才能拉出Content
event.offsetLocation(initX, initY); // 在X,Y的方向上分别加上initX,initY。
/*
* 不加这一句会使distanceX的值差异非常大,而distanceX会影响到mTranceX。
*/
/*
* mTranceX是有范围的,如果最小值小于这个范围就不会在发生该变了, 所有 会出现延迟的现象。其实那是还没有到达最小值。
*/
}
// if 手势监听器没有处理
// if 用户事件为抬起事件 启动动画线程
boolean result = mGestureDetector.onTouchEvent(event);
if (!result)
{
if (action == MotionEvent.ACTION_UP)
{
// tup up after scrolling
post(startAnimation);
}
}
return false;
}
};
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;// ???() 动画的起始、终点坐标。相对于器parent View
// if 控件状态为Fling
// 判断是否处于收缩状态
if (mState == State.FLYING)
{
mIsShrinking = (mPosition == TOP || mPosition == LEFT) ^ (mVelocity > 0);
}
// if 控件为纵向
// 获取Y方向Delta
// if 控件是拖动
// 计算周期时间
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 计算周期为0
// toXDelta跟fromXDelta相等
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;
}
};
/**
* 处理背景的drawable
*/
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;
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;
}
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)
{
}
/*
* 该变mTranceX和mTranceY,更新UI
*/
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)
{
}
public boolean onSingleTapUp(MotionEvent e)
{
// simple tap: click
post(startAnimation);
return true;
}
}
}
1.要想让您的控件水平居中或垂直居中其实很简单,只要在控件的上一级中 android:gravity="center"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:background="#000000"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageView
android:id="@+id/logo"
android:src="@drawable/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
2.全局全屏
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
>
3.横屏
<activity
android:label="@string/app_name"
android:name=".HousesActivity"
android:screenOrientation="landscape">