观察如图2-4 的完整项目中的效果界面,点击标题栏的左上角会弹出侧边栏,再次点击时会关闭侧边栏,这种效果在很多手机应用中使用,因此,我们有必要学会如何自定义一个具有侧边栏效果的控件。
在本章中,主界面为MainActivity.java,具体代码如文件所示:res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.itheima.slidmenudemo.view.SlidMenu
android:id="@+id/slidmenu"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include layout="@layout/slidmenu_left" />
<include layout="@layout/slidmenu_main"/>
com.itheima.slidmenudemo.view.SlidMenu>
RelativeLayout>
其中,slidmenu_left.xml 是侧边栏的布局文件,具体代码如文件【2-7】所示:【文件2-7】res/layout/slidmenu_left.xml
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="240dp"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="240dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/menu_bg" >
<ImageButton
style="@style/tab_style"
android:text="新闻"
android:background="#33000000"
android:drawableLeft="@drawable/tab_news" />
<ImageButton
style="@style/tab_style"
android:text="订阅"
android:drawableLeft="@drawable/tab_read" />
<TextView
style="@style/tab_style"
android:text="本地"
android:drawableLeft="@drawable/tab_local" />
<TextView
style="@style/tab_style"
android:text="跟帖"
android:drawableLeft="@drawable/tab_ties" />
<TextView
style="@style/tab_style"
android:text="图片"
android:drawableLeft="@drawable/tab_pics" />
<TextView
style="@style/tab_style"
android:text="话题"
android:drawableLeft="@drawable/tab_ugc" />
<TextView
style="@style/tab_style"
android:text="投票"
android:drawableLeft="@drawable/tab_vote" />
<TextView
style="@style/tab_style"
android:text="聚合阅读"
android:drawableLeft="@drawable/tab_focus" />
LinearLayout>
ScrollView>
上面的代码中我们在styles.xml 文件中定义了一个样式tab_style,它是用来修饰左边侧栏中每一个条目,详细代码如下所示。
<style name="tab_style">
<item name="android:padding">5dpitem>
<item name="android:gravity">center_verticalitem>
<item name="android:drawablePadding">5dpitem>
<item name="android:layout_width">match_parentitem>
<item name="android:layout_height">wrap_contentitem>
<item name="android:textSize">18spitem>
<item name="android:background">@drawable/tab_bg
- "android:textColor"
>#ffffff
- "android:onClick"
>clickTab
- "android:focusable">true
- "android:clickable">true
style>
主界面的右边slidmenu_main.xml 布局文件的代码如文件【2-8】所示。【文件2-8】res/layout/slidmenu_left.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/top_bar_bg"
android:orientation="horizontal" >
<ImageButton
android:id="@+id/main_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/main_back" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:background="@drawable/top_bar_divider" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:text="黑马新闻"
android:textColor="#fff"
android:textSize="24sp" />
LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="钓鱼岛是中国的!!!!\nXXX 是世界的"
android:textColor="#000"
android:textSize="24sp" />
LinearLayout>
自定好控件之后,MainActivity,java 主界面的业务逻辑如下所示。activity_setting.xml 如文件【2-9】所示:【文件2-9】res/layout/setup_password_dialog.xml
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
ImageButton main_back = (ImageButton) findViewById(R.id.main_back);
final SlidMenu slidmenu = (SlidMenu) findViewById(R.id.slidmenu);
main_back.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
boolean currentView = slidmenu.isMenuShow();
if(currentView){//当前菜单界面显示,就隐藏
slidmenu.hideMenu();
}else{
slidmenu.showMenu();
}
}
});
}
public void clickTab(View v){
TextView tv = (TextView) v;
Toast.makeText(getApplicationContext(), tv.getText(), 0).show();
}
}
侧边栏在项目会经常用到,这里我们将自定义一个侧边栏,具体代码如文件【2-10】所示。【文件2-10】com/itheima/slidmenudemo/view/SlidMenu.java
public class SlidMenu extends ViewGroup {
private int downX;
private final int MAIN_VIEW = 0;// 主界面
private final int MENU_VIEW = 1;// 左边菜单界面
private int currentView = MAIN_VIEW;// 记录当前界面,默认为主界面
private Scroller scroller;// 用来模拟数据
private int touchSlop;
public SlidMenu(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SlidMenu(Context context) {
this(context, null);
}
@SuppressWarnings("deprecation")
private void init() {
scroller = new Scroller(getContext());
touchSlop = ViewConfiguration.getTouchSlop();// 系统默认你进行了一个滑动操作的固定值
}
/**
* widthMeasureSpec 屏幕的宽度heightMeasureSpec 屏幕的高度
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 测量主界面
View mainView = getChildAt(1);// 主界面
// 主界面宽高:屏幕的宽度屏幕的高度
mainView.measure(widthMeasureSpec, heightMeasureSpec);
// 测量左边菜单界面和主界面的宽高
View menuView = getChildAt(0);// 左边菜单界面
menuView.measure(menuView.getLayoutParams().width, heightMeasureSpec);
}
// viewgroup 的排版方法
/**
* int l 左边0 int t 上边0 int r 右边屏幕的宽度int b 下边屏幕的高度
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 排版主界面左边0, 高度0, 右边屏幕宽度,下边屏幕高度
View mainView = getChildAt(1);
mainView.layout(l, t, r, b);
// 排版左边菜单界面左边-左边菜单界面的宽度, 高度0 ,右边0, 下边屏幕高度
View menuView = getChildAt(0);
menuView.layout(-menuView.getMeasuredWidth(), t, 0, b);
}
// 按下的点: downX = 100
// 移动过后的点: moveX = 150
// 间距diffX = -50 =100 -150 = downX - moveX;
// scrollby(diffX,0)
// downX = moveX = 150
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
// 计算间距
int diffX = downX - moveX;
int scrollX = getScrollX();// 获得当前界面的左上角的X 值
// 屏幕将要移动到的X 值
int dff = scrollX + diffX;
if (dff < -getChildAt(0).getMeasuredWidth()) {// 设置左边界
scrollTo(-getChildAt(0).getMeasuredWidth(), 0);
} else if (dff > 0) {// 设置右边界
scrollTo(0, 0);
} else {// 如果不超过边界值,就要根据屏幕左上角的点的X 值,来计算位置
scrollBy(diffX, 0);
}
downX = moveX;
break;
case MotionEvent.ACTION_UP:
int center = -getChildAt(0).getMeasuredWidth() / 2;
if (getScrollX() > center) {
System.out.println("显示主界面");
currentView = MAIN_VIEW;
} else {
System.out.println("显示左边菜单界面");
currentView = MENU_VIEW;
}
switchView();
break;
default:
break;
}
return true;// 由我们自己来处理触摸事件
}
// 根据当前状态值来切换切面
private void switchView() {
int startX = getScrollX();
int dx = 0;
if (currentView == MAIN_VIEW) {
// scrollTo(0, 0);
dx = 0 - startX;
} else {
// scrollTo(-getChildAt(0).getMeasuredWidth(), 0);
dx = -getChildAt(0).getMeasuredWidth() - startX;
}
int duration = Math.abs(dx) * 10;
if (duration > 1000) {
duration = 1000;
}
scroller.startScroll(startX, 0, dx, 0, duration);
invalidate();
// scrollTo(scroller.getCurrX(), 0);
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
int currX = scroller.getCurrX();
scrollTo(currX, 0);
invalidate();
}
}
// 判断当前是否是菜单界面,true 就是菜单界面
public boolean isMenuShow() {
return currentView == MENU_VIEW;
}
// 隐藏菜单界面
public void hideMenu() {
currentView = MAIN_VIEW;
switchView();
}
// 显示菜单界面
public void showMenu() {
currentView = MENU_VIEW;
switchView();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) ev.getX();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) ev.getX();
int diff = moveX - downX;
if (Math.abs(diff) > touchSlop) {
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
}
自定义好侧边栏之后,运行程序,效果图如图2-5 所示。
package com.github.slidingmenu.viewdraghelper;
import android.content.Context;
import android.graphics.Color;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.support.v4.widget.ViewDragHelper.Callback;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
public class SlideMenu2 extends FrameLayout{
private String TAG = SlideMenu2.class.getSimpleName();
public SlideMenu2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private ViewDragHelper viewDragHelper;
private void init(){
viewDragHelper = ViewDragHelper.create(this, callback);
}
private View menuView,mainView;
private int menuWidth,menuHeight,mainWidth;
private int dragRange;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(getChildCount()<2){
throw new IllegalStateException("Your layout must has 2 children or more!");
}
menuView = getChildAt(0);
mainView = getChildAt(1);
setBackgroundColor(Color.BLACK);
}
// @Override
// protected void onLayout(boolean changed, int left, int top, int right,
// int bottom) {
// super.onLayout(changed, left, top, right, bottom);
// menuView.layout(left, top, right, bottom);
// mainView.layout(left, top, right, bottom);
// }
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
menuWidth = menuView.getMeasuredWidth();
menuHeight = menuView.getMeasuredHeight();
mainWidth = mainView.getMeasuredWidth();
dragRange = (int) (menuWidth * 0.6);
// ViewHelper.setScaleX(menuView, 0.5f);
// ViewHelper.setScaleY(menuView, 0.5f);
// ViewHelper.setTranslationX(menuView, -dragRange/2);
}
private int lastX,lastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
if(mStatus==Status.Open && viewDragHelper.isViewUnder(mainView, x, y)){
return true;
}
// Log.e(TAG, "onInterceptTouchEvent : "+);
if(viewDragHelper.isViewUnder(mainView, x, y)){
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
int deltaX = x - lastX;
int deltaY = y - lastY;
if(Math.abs(deltaX)>Math.abs(deltaY)*2) {
// Log.e(TAG, "移动斜角太大,拦截事件");
viewDragHelper.cancel();
return true;
}
break;
case MotionEvent.ACTION_UP:
lastX = 0;
lastY = 0;
break;
}
lastX = x;
lastY = y;
}
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
if(mStatus==Status.Open && viewDragHelper.isViewUnder(mainView, x, y)){
if(event.getAction()==MotionEvent.ACTION_UP){
Log.e(TAG, "抬起");
close();
}
}
viewDragHelper.processTouchEvent(event);
return true;
}
private Callback callback = new Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child==menuView || child==mainView;
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
// Log.e(TAG, "onViewPositionChanged dx: "+dx);
if(changedView==menuView){
menuView.layout(0, 0, menuWidth, menuHeight);
if(mainView.getLeft()>dragRange){
mainView.layout(dragRange, 0, dragRange+mainWidth, mainView.getBottom());
}else {
mainView.layout(mainView.getLeft()+dx, 0, mainView.getRight()+dx, mainView.getBottom());
}
}
float percent = mainView.getLeft()/(float)dragRange;
excuteAnimation(percent);
if(mainView.getLeft()==0 && mStatus != Status.Close){
mStatus = Status.Close;
if(onDragStatusChangeListener!=null ){
onDragStatusChangeListener.onClose();
}
}else if (mainView.getLeft()==dragRange && mStatus != Status.Open) {
mStatus = Status.Open;
if(onDragStatusChangeListener!=null ){
onDragStatusChangeListener.onOpen();
}
}else {
if(onDragStatusChangeListener!=null){
onDragStatusChangeListener.onDragging(percent);
}
}
}
private void excuteAnimation(float percent) {
menuView.setScaleX(0.5f + 0.5f * percent);
menuView.setScaleY(0.5f + 0.5f * percent);
mainView.setScaleX(1 - percent * 0.2f);
mainView.setScaleY( 1 - percent * 0.2f);
menuView.setTranslationX( -dragRange / 2 + dragRange / 2 * percent);
menuView.setAlpha(percent);
getBackground().setAlpha((int) ((1 - percent) * 255));
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// Log.e(TAG, "onViewReleased :"+(releasedChild==mainView));
if(mainView.getLeft()>dragRange/2){
open();
}else {
close();
}
}
@Override
public int getViewHorizontalDragRange(View child) {
return menuWidth;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if(child==mainView){
if(left<0)return 0;
if(left>dragRange) return dragRange;
}
if(child==menuView){
mainView.layout(mainView.getLeft()+dx, 0, mainView.getRight()+dx, mainView.getBottom());
menuView.layout(0, 0, menuWidth, menuHeight);
return 0;
}
return left;
}
};
public void open(){
viewDragHelper.smoothSlideViewTo(mainView, dragRange, 0);
ViewCompat.postInvalidateOnAnimation(SlideMenu2.this);
}
public void close(){
viewDragHelper.smoothSlideViewTo(mainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(SlideMenu2.this);
}
public void computeScroll() {
if(viewDragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
public OnDragStatusChangeListener getOnDragStatusChangeListener() {
return onDragStatusChangeListener;
}
public void setOnDragStatusChangeListener(OnDragStatusChangeListener onDragStatusChangeListener) {
this.onDragStatusChangeListener = onDragStatusChangeListener;
}
public Status mStatus = Status.Close;;
public enum Status{
Open,Close
}
private OnDragStatusChangeListener onDragStatusChangeListener;
public interface OnDragStatusChangeListener{
void onOpen();
void onClose();
void onDragging(float dragProgress);
}
}
代码:https://github.com/JackChen1999/SlidingMenu