1.该自定义View是一个ViewGroup,由主界面和滑动界面Drawer组成,如图所示:
View完整代码如下
public class SlideMenuextends ViewGroup {
private View leftMenu;
private View mainView;
private float moveX;
private float downX;
private int curScrollPosition;
private int cur_state = 0;//当前状态
private static final int MAIN_STATE = 0;
private static final int MENU_STATE = 1;
private Scroller scroller;//滚动器
private float downY;
public SlideMenu(Context context) {
super(context);
initAnim();
}
public SlideMenu(Context context, AttributeSet attrs) {
super(context, attrs);
initAnim();
}
public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAnim();
}
//初始化滚动器
private void initAnim() {
scroller = new Scroller(getContext());
}
/**
* 测量并设置所有子View宽高
* @param widthMeasureSpec:当前控件的宽度测量规则
* @param heightMeasureSpec:当前控件的高度测量规则
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//指定左面板的宽高
leftMenu = getChildAt(0);//第一个子View
//leftMenu.measure(leftMenu.getMeasuredWidth(), heightMeasureSpec);
leftMenu.measure(leftMenu.getLayoutParams().width, heightMeasureSpec);
//指定主面板的宽高
mainView = getChildAt(1);//第二个子View
mainView.measure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* b:当前控件的尺寸大小、位置是否发生了变化
*/
@Override
protected void onLayout(boolean isChanged, int l, int t, int r, int b) {
//放置在侧滑面板(相对于左上角原点位置)
leftMenu.layout(-leftMenu.getMeasuredWidth(), 0, 0, b);
mainView.layout(l, t, r, b);
}
/**
*事件拦截
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//根据偏移量判断是否拦截
float offsetX = Math.abs(event.getX() - downX);
float offsetY = Math.abs(event.getY() - downY);
if (offsetX > offsetY && offsetX > 5) {
return true;//拦截触摸事件,不往里传递,交个自己的OnTouchEvent处理
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(event);//默认不拦截
}
/**
* 滚动屏幕,将View看做静的,屏幕是动的
* scrollBy(x,y) :在原来位置基础上滚动了
* scrollTo(x,y) : 滚动到(x,y)位置
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();//按下时X坐标
break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX();//
int x_change = (int) (downX - moveX);//x变化(偏移量)
//当前要滚动的位置 = 之前滚动到的位置 + 偏移量
curScrollPosition = getScrollX() + x_change;//最新滚动位置
/**
* 考虑边界问题
*/
if (curScrollPosition > 0) {
scrollTo(0, 0);
} else if (curScrollPosition < -leftMenu.getMeasuredWidth()) {
scrollTo(-leftMenu.getMeasuredWidth(), 0);
} else {
scrollBy(x_change, 0);//(在范围内)滚动到当前位置
}
downX = moveX;//重要
break;
case MotionEvent.ACTION_UP:
int menu_center = (int) (-leftMenu.getMeasuredWidth() / 2.0f);
if (getScrollX() < menu_center) {
//打开
cur_state = MENU_STATE;
} else {
cur_state = MAIN_STATE;
}
updateView(cur_state);
break;
}
return true;//一定改成true(消费事件)
}
//滑动过程中松开手
private void updateView(int cur_state) {
int startX = getScrollX();//负数(当前滑动到的位置)
int dx = 0;//将要移动的偏移量
if (cur_state == MAIN_STATE) {//关闭
dx = 0 - startX;
} else {
dx = -leftMenu.getMeasuredWidth() - startX;
}
//开始平滑的数据模拟
int duration = Math.abs(dx * 2);
//执行动画
scroller.startScroll(startX, 0, dx, 0, duration);
invalidate();//重绘界面:drawChild--computeScroll()
}
//维持动画
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {//true动画没结束时(duration时间内)一直调用
int currX = scroller.getCurrX();//获取当前的模拟值,要滚动的位置
scrollTo(currX, 0);
invalidate();//重绘->drawChild--computeScroll()
}
}
//手动设置关闭菜单栏
public void switcheDrawer() {
if (cur_state == MAIN_STATE) {//主
cur_state = MENU_STATE;
} else {
cur_state = MAIN_STATE;
}
updateView(cur_state);
}
}
2.自定义View布局
该view是一个ViewGroup,里面有两个子View
<!-- 这是一个ViewGroup ,里面两个子View-->
<com.sliding.SlideMenu
android:id="@+id/sm"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<!-- 第一个子View-->
<include layout="@layout/layout_menu_left"/>
<!--第两个子View-->
<include layout="@layout/layout_main" />
</com.sliding.SlideMenu>
3.测量子View宽高
对ViewGroup所有子View的宽高进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量第一个子View的宽高
leftMenu = getChildAt(0);
leftMenu.measure(leftMenu.getLayoutParams().width, heightMeasureSpec);
//测量第二个子View的宽高
mainView = getChildAt(1);
mainView.measure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
4.放置每个子View
@Override
protected void onLayout(boolean isChanged, int l, int t, int r, int b) {
//第一个子View的位置
leftMenu.layout(-leftMenu.getMeasuredWidth(), 0, 0, b);
//第二个子View的位置
mainView.layout(l, t, r, b);
}
5.滑动过程中的触摸事件(重要)
滚动屏幕,将View看做静的,屏幕是动的
scrollBy(x,y) :在原来位置基础上滚动了(x,y)
scrollTo(x,y) : 滚动到(x,y)位置
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();//按下时X坐标
break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX();//
int x_change = (int) (downX - moveX);//x变化(偏移量)
//当前要滚动的位置 = 之前滚动到的位置 + 偏移量
curScrollPosition = getScrollX() + x_change;//最新滚动位置
/**
* 考虑边界问题
*/
if (curScrollPosition > 0) {
scrollTo(0, 0);
} else if (curScrollPosition < -leftMenu.getMeasuredWidth()) {
scrollTo(-leftMenu.getMeasuredWidth(), 0);
} else {
scrollBy(x_change, 0);//(在范围内)滚动到当前位置
}
downX = moveX;//重要点
break;
case MotionEvent.ACTION_UP:
int menu_center = (int) (-leftMenu.getMeasuredWidth() / 2.0f);
if (getScrollX() < menu_center) {
cur_state = MENU_STATE;
} else {
cur_state = MAIN_STATE;
}
updateView(cur_state);
break;
}
return true;//一定改成true(消费事件)
}
最后松手时,需要让View的Drawer处于开或者闭的状态
//滑动过程中松开手
private void updateView(int cur_state) {
int startX = getScrollX();//负数(当前滑动到的位置)
int dx = 0;//将要移动的偏移量
if (cur_state == MAIN_STATE) {//关闭
dx = 0 - startX;
} else {
dx = -leftMenu.getMeasuredWidth() - startX;
}
int duration = Math.abs(dx * 2);//动画执行时间可以自行设置
//Android自带的滚动器执行动画
scroller.startScroll(startX, 0, dx, 0, duration);
invalidate();//重绘界面:drawChild--computeScroll()
}
//维持动画
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {//true动画没结束时(duration时间内)一直调用
int currX = scroller.getCurrX();//获取当前的模拟值,要滚动的位置
scrollTo(currX, 0);
invalidate();//重绘->drawChild--computeScroll()
}
}
6.事件冲突问题
由于Drawer是一个ScrollView,在上下滑动与左右滑动会有事件冲突,这时候需要对事件传递进行拦截处理,交给自己的OnTouchEvent()处理,否则继续传给子View处理
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//根据偏移量判断是否拦截
float offsetX = Math.abs(event.getX() - downX);
float offsetY = Math.abs(event.getY() - downY);
//当左右滑动偏移量大于上下滑动偏移量,且偏移量大于5时(),进行拦截处理
if (offsetX > offsetY && offsetX > 5) {
return true;//拦截触摸事件,不往里传递,交个自己的OnTouchEvent处理
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(event);//默认返回false不拦截
}
7.程序入口
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SlideMenu slideMenu = findViewById(R.id.sm);
ImageButton imageButton= findViewById(R.id.main_back);
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId()==R.id.main_back){
//手动设置开关菜单栏
slideMenu.switcheDrawer();
}
}
});
}
}
欢迎大佬批评指正!