Android自定义控件——手把手教你实现SlidingMenu(一)

时间:2022-01-20 18:05:34

SlidingMenu是Github上知名的开源项目,在这里就不多做介绍了。

本系列文章教大家分析和实现一个SlidingMenu。就SlidingMenu实现的功能点来说

  • 右滑动在当前View,下面View充当菜单      (自定义ViewGroup)
  • 在内容部分上下,左右滑动做不同的事        (事件分发)
  • 左右快速滑动时的,FlingAction                 (VelocityTracker,Fling就是指你手指在控件上快速的滑动一下然后松开手指之后要做的事情)

系列一           第一篇主要来分析整个项目所需要的类,比如如何把set给Activity的ContentView拿出来放在我们自己的ViewGroup中           很明显,SlidingMenu在右滑的时候当ContentView像右侧滑动的时候,MenuView是出于ContentView下面的,所以这个SlidingMenu需要的ViewGroup我们使用RelativeLayout就可以了,两个View重叠在一起,当我们滑动上面的View的时候,将它滚动到一定的位置即可。还需要的话就是装内容部分的contentView,装菜单的menuView部分,看一下我们需要那些类:
Android自定义控件——手把手教你实现SlidingMenu(一)

MainActivity                  ——测试demo的主界面 SlidingActionBarActivity——重写继承自v7的ActionBarActivity SlidingActivity               ——重写Actiivity SlidingBaseActivity        ——定义了SlidingMenu会用到的接口 SlidingMenu                  ——继承自RelatieLayout的容器 SlidingMenuAbove       ——继承自ViewGroup的ContentView部分 SlidingMenuBehind      ——继承自ViewGroup的MenuView部分 SlidingMenuHelper       ——初始化SlidingMenu的帮助类,提供给重写Activity时使用


SlidingMenu.java SlidingMenu在初始化的时候,依次添加contentView和menuView。并且留给接口把Activity的contentView赋值到SlidingMenu的contentView中,这个很好理解,就是你初始化Activity的时候不是把view   set到Avtivity里了么,那么他是怎么跑到SlidingMenu中的呢?如下代码:
public class SlidingMenu extends RelativeLayout {

// private LinearLayout mLayout;

// private LinearLayout mLayout2;

private SlidingMenuAbove mAbove;

private SlidingMenuBehind mBehind;
/**
* 填充Content的两种样式
*/
public static final int SLIDING_WINDOW = 0;
public static final int SLIDING_CONTENT = 1;

public static final int LEFT = 0;
public static final int RIGHT = 1;
public static final int LEFT_RIGHT = 2;

public SlidingMenu(Context context) {
this(context, null);
}

public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initTestView();
mAbove = new SlidingMenuAbove(context);
mBehind = new SlidingMenuBehind(context);
LayoutParams aboveParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
LayoutParams behindParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
addView(mBehind, behindParams);
addView(mAbove, aboveParams);

}

public SlidingMenu(Activity activity, int slidstyle) {
this(activity, null);
this.attachToActivity(activity, slidstyle);
}

// @Override
// protected void onFinishInflate() {
// super.onFinishInflate();
// // mLayout = (LinearLayout) findViewById(R.id.view1);
// // mLayout2 = (LinearLayout) findViewById(R.id.view2);
// }

/**
* 添加测试View
*/
private void initTestView() {

}

/**
* 重载的attachToActivity方法
*
* @param activity
* 当前的Activity
* @param slidstyle
* 设置content的方式
*/
public void attachToActivity(Activity activity, int slidstyle) {
attachToActivity(activity, slidstyle, false);
}

/**
* 获取Activity的content视图,获取方式有window和content两种方式,
* 获取之后把它设置成为SlidingMenu的content部分,之后再把SlidingMenu整个视图设置为Activity的content
*
* @param activity
* 当前的Activity视图
* @param slidstyle
* 设置content的 方式
* @param actionbarOverlay
* actionBar是否跟随
*/
public void attachToActivity(Activity activity, int slidstyle, boolean actionbarOverlay) {

TypedArray array = activity.getTheme().obtainStyledAttributes(new int[] { android.R.attr.windowBackground });
int background = array.getResourceId(0, 0);
array.recycle();
switch (slidstyle) {
case SLIDING_WINDOW:
ViewGroup decorview = (ViewGroup) activity.getWindow().getDecorView();
ViewGroup decorChild = (ViewGroup) decorview.getChildAt(0);
decorChild.setBackgroundResource(background);
decorview.removeView(decorChild);
decorview.addView(this);
setContent(decorChild);
break;
case SLIDING_CONTENT:
ViewGroup contentParent = (ViewGroup) activity.findViewById(android.R.id.content);
View contentChild = contentParent.getChildAt(0);
contentParent.removeView(contentChild);
contentParent.addView(this);
setContent(contentChild);
if (contentChild.getBackground() == null) {
contentChild.setBackgroundResource(background);
}
break;
default:
break;
}

}

public void setContent(View content) {
// mLayout2.removeView(content);
// mLayout2.addView(content);
mAbove.setContent(content);
}

}

SlidigMenuAbove.java
继承自ViewGroup,简单的重写onLayout,onMesure即可
public class SlidingMenuAbove extends ViewGroup {

private final String TAG = SlidingMenuAbove.class.getSimpleName();
private boolean DEBUG = false;

private Scroller mScroller;
private int mTouchSlop;
private VelocityTracker mTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mFlingDistance;

private boolean mEnabled = true;
private boolean mIsBeingDraged;
private boolean mIsUnableToDrag;
private boolean mQuickReturn;

private float mLastMotionX;
private float mLastMotionY;
private float mInitialMotionX;

private int mCurItem;
private float mScrollX = 0.0f;

protected int mActivePointerID = INVALID_POINTER;
private static final int INVALID_POINTER = -1;

private final static int MAX_SETTLE_DURATION = 600;
private final static int MIN_DISTANCE_FOR_FLING = 25;
private final static Interpolator S_INTERPOLATOR = new Interpolator() {

@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};

private View mContentView;

public SlidingMenuAbove(Context context) {
this(context, null);
}

public SlidingMenuAbove(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

@SuppressLint("ResourceAsColor")
public SlidingMenuAbove(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAboveView();
}

private void initAboveView() {//这个先不用管,后面介绍
setWillNotDraw(false);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
Context context = getContext();
mScroller = new Scroller(context, S_INTERPOLATOR);
ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

/**
* 密度,和判定为Fling动作的最小距离
*/
final float density = context.getResources().getDisplayMetrics().density;
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);

}

@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
mContentView.layout(arg1, arg2, arg3, arg4);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);
int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);
mContentView.measure(contentWidth, contentHeight);
}

public void setContent(View content) {
if (mContentView != null) {
this.removeView(mContentView);
}
mContentView = content;
addView(mContentView);
}



}

SlidingMenuBehind.java
继承自ViewGroup,简单的重写onLayout,onMesure。和SlidingMenuAbove类似
public class SlidingMenuBehind extends ViewGroup {

private TextView mTextView;

public SlidingMenuBehind(Context context) {
this(context, null);
}

public SlidingMenuBehind(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

@SuppressLint("ResourceAsColor")
public SlidingMenuBehind(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setBackgroundColor(android.R.color.holo_green_light);
mTextView = new TextView(context);
mTextView.setTextSize(50);
mTextView.setText(SlidingMenuBehind.class.getSimpleName());
addView(mTextView);
}

@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
mTextView.layout(arg1, arg2, arg3, arg4);
}

}


主要的三个容器类的雏形就写好了,下面我们结合Activity来看一下如何使用
SlidingMenuHelper.java 重写Activity的时候通过此类在Activity生命周期的合适位置初始化SlidingMenu
public class SlidingMenuHelper {

/**
* Activity
* <a>在给SlidingMenu的ContentView绑定当前的Activity的ContentView视图的时候会用到</a>
*/
private Activity mActivity;

private SlidingMenu mSlidingMenu;

public SlidingMenuHelper(Activity activity) {
mActivity = activity;
}

/**
* 对应Activity完成相应的初始化工作
*
* @param savedInstanceState
* 来自Activity的Bundle
*/
public void onCreate(Bundle savedInstanceState) {
mSlidingMenu = (SlidingMenu) LayoutInflater.from(mActivity).inflate(R.layout.sliding_menu, null);
}

/**
* 当Activity再次加载时,比如,长时间锁屏,横竖屏切换时,读取从Bundle里存入的值
*
* @param savedInstanceState
* 填充Activity的Content的方式,window和content
*/
public void onPostCreate(Bundle savedInstanceState) {
mSlidingMenu.attachToActivity(mActivity, SlidingMenu.SLIDING_WINDOW);
}

public View findViewById(int layoutId) {
View v;
if (mSlidingMenu != null) {
v = mSlidingMenu.findViewById(layoutId);
if (v != null) {
return v;
}
}
return null;
}

public SlidingMenu getSlidingMenu() {
return mSlidingMenu;
}

}

SlidingBaseActvity.java定义SlidingMenu的统一接口
/**
* 侧滑菜单接口
*
* @author mingwei
*
*/
public interface SlidingBaseActivity {

/**
* 设置位于SlidingMenu后面的菜单视图
*
* @param view
* 菜单视图对象
* @param params
* 菜单视图的布局参数
*/
public void setBehindContentView(View view, LayoutParams params);

/**
* 设置位于SlidingMenu后面的菜单视图
*
* @param view
* 菜单视图对象
*/
public void setBehindContentView(View view);

/**
* 设置位于SlidingMenu后面的菜单视图
*
* @param layoutId
* 菜单的布局id
*/
public void setBehindContentView(int layoutId);

/**
* 返回当前SlidingActivity的SlidingMenu对象
*
* @return
*/
public SlidingMenu getSlidingMenu();

/**
* 菜单切换
*/
public void toggle();

/**
* 显示Content部分
*/
public void showContent();

/**
* 显示Menu部分
*/
public void showMenu();

/**
* 显示第二Menu部分
*/
public void showSecondaryMenu();

/**
* 设置ActionBar是否跟随Content
*
* @param enable
* ActionBar与Content是否联动
*/
public void setSlidingActionBarEnabled(boolean enable);
}



自定义的SlidingActivity.java    在相应的生命周期函数中调用SlidingMenuHelper.java
public class SlidingActivity extends Activity implements SlidingBaseActivity {

SlidingMenuHelper mHelper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHelper = new SlidingMenuHelper(this);
mHelper.onCreate(savedInstanceState);
}

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mHelper.onPostCreate(savedInstanceState);
}

public View findViewById(int layoudId) {
View v = super.findViewById(layoudId);
if (v != null) {
return v;
}
return mHelper.findViewById(layoudId);
}

@Override
public void setContentView(int layoutResID) {
setContentView(getLayoutInflater().inflate(layoutResID, null));
}

@Override
public void setContentView(View view) {
setContentView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}

@Override
public void setContentView(View view, LayoutParams params) {
super.setContentView(view, params);
}

@Override
public void setBehindContentView(View view, LayoutParams params) {

}

@Override
public void setBehindContentView(View view) {

}

@Override
public void setBehindContentView(int layoutId) {

}

@Override
public SlidingMenu getSlidingMenu() {
return mHelper.getSlidingMenu();
}

@Override
public void toggle() {

}

@Override
public void showContent() {

}

@Override
public void showMenu() {

}

@Override
public void showSecondaryMenu() {

}

@Override
public void setSlidingActionBarEnabled(boolean enable) {

}

}


SlidingActivityDemo01.java
public class SlidingActivityDemo1 extends SlidingActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sliding_activity_demo1);
//TextView
}
}

效果:
Android自定义控件——手把手教你实现SlidingMenu(一)

下篇分析一下简单的滚动事件