先看效果图:
先来说说思路:我们把该页面分为两部分,分别是头部的抽屉布局(海洋色背景)和主内容布局(白色背景),这两部分的布局是呈线性关系,即抽屉在上,主页面在下,并且它们的父布局应该是一个可滑动的LinearLayout线性布局
所以,我们的目标就是自定义一个可滑动的LinearLayout,并且设置它的子布局都向上移动一个自定义LinearLayout的高度
一、自定义控件的测量和布局
自定义LinearLayout,假设为MyPullDownLayout:
public class MyPullDownLayout extends LinearLayout {
public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
我们先对子控件进行简单的测量:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量子控件的大小
for(int i=0;i<getChildCount();i++){
View view=getChildAt(i);
measureChild(view,widthMeasureSpec,heightMeasureSpec);
}
}
在onLayout中设置每一个子控件的位置:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l,t, r, b);
int height=getVerticalHeight();
//设置子控件的位置
for(int i=0;i<getChildCount();i++){
View view=getChildAt(i);
view.layout(view.getLeft(),view.getTop()-height,view.getRight(),view.getBottom()-height);
}
}
private int getVerticalHeight(){
return getHeight()-getPaddingBottom()-getPaddingTop();
}
int height = getVerticalHeight() 即为这个自定义控件的高度,并让每个子控件都向上移动一个height的高度
二、自定义控件的滑动实现
我们创建一个手势检测器(GestureDetector)来辅助我们滑动控件:
private int currentPos=0;
private GestureDetector detector=new GestureDetector(new GestureDetector.SimpleOnGestureListener(){
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
//相对滑动:Y方向滑动多少距离,view就跟着滑动多少距离
//手指向上滑动
if(e2.getY()<e1.getY()&&getScrollY()>=0){
//滑到的y值超过0时,反弹回去
if(getScrollY()>0)scrollBy(0,-getScrollY());
//否则什么也不做
}
else if(getScrollY()<=0){
//如果手指向下滑动并且没有超过抽屉页的滑动范围,就滑动页面
if(!(e2.getY()>e1.getY()&&getScrollY()==-getVerticalHeight())){
scrollBy(0,(int)distanceY);
//手指向下滑动
if(e2.getY()>e1.getY())currentPos=getScrollY()/(getVerticalHeight()/4);
//手指向上滑动
else currentPos=getScrollY()/(4*getVerticalHeight()/5);
if(currentPos>0)currentPos=0;
if(currentPos<-1)currentPos=-1;
}
}
return super.onScroll(e1,e2,distanceX,distanceY);
}
});
@Override
public boolean onTouchEvent(MotionEvent event) {
//将event信息传给detector;
detector.onTouchEvent(event);
}
private int getVerticalHeight(){
return getHeight()-getPaddingBottom()-getPaddingTop();
}
getScrollY():y轴方向已经滑动的距离。如果在抽屉页,则getScrollY()为负数
currentPos:手指离开屏幕后,控件所在的位置(抽屉页或者主页面)
currentPos=getScrollY()/(getVerticalHeight()/4):只需滑动1/4控件高度的距离就可以自动滑动到抽屉页
currentPos=getScrollY()/(4*getVerticalHeight()/5):只需滑动1-4/5=1/5控件高度的距离就可以自动滑动到主页面
创建Scorller对象来实现页面的自动滑动效果:
private Scroller scroller;
public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
scroller=new Scroller(getContext());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将event信息传给detector;
detector.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_UP:
//手指离开屏幕后开始自动滑动
scroller.startScroll(0,getScrollY(),0,getVerticalHeight()*currentPos-getScrollY());
invalidate();
break;
}
return true;
}
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()){
scrollTo(0,scroller.getCurrY());
postInvalidateDelayed(10);
}
}
scroller.startScroll():前两个参数是x,y方向已经滑动的距离,后两个参数是x,y方向还需要滑动的距离
computeScroll():利用scroller.getCurrY()每次滑动一点点距离,调用postInvalidateDelayed(10)后会回调该方法
三、XML布局文件注意事项
为了体现抽屉控件的宽高占整个父控件宽高的效果,需要把抽屉布局(Layout)的width和height设为match_parent。然后主页面布局部分就放在抽屉布局的下方,具体代码如下:
<?xml version="1.0" encoding="utf-8"?>
<com.myviewtext.MyPullDownLayout
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="match_parent"
android:orientation="vertical"
android:background="@mipmap/bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="蜀道之难,难于上青天!"
android:textColor="@color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="蚕丛及鱼凫,开国何茫然!"
android:textColor="@color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。"
android:textColor="@color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="地崩山摧壮士死,然后天梯石栈相钩连。"
android:textColor="@color/white"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="臣本布衣,躬耕南阳,苟全性命于乱世,不求闻达于诸侯。"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,谘臣以当世之事,由是感激,遂许先帝以驱驰。"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="后值倾覆,受任于败军之际,奉命于危难之间:尔来二十有一年矣。"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧虑,恐付托不效,以伤先帝之明;故五月渡泸,深入不毛。"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="今南方已定,甲兵已足,当奖帅三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。"/>
</com.hualinfo.myviewtext.MyPullDownLayout>
自定义LinearLayout完整代码如下:
public class MyPullDownLayout extends LinearLayout {
private int currentPos=0;
private Scroller scroller;
public MyPullDownLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量子控件的大小
for(int i=0;i<getChildCount();i++){
View view=getChildAt(i);
measureChild(view,widthMeasureSpec,heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l,t, r, b);
int height=getVerticalHeight();
//设置子控件的位置
for(int i=0;i<getChildCount();i++){
View view=getChildAt(i);
view.layout(view.getLeft(),view.getTop()-height,view.getRight(),view.getBottom()-height);
}
}
private void init(){
scroller=new Scroller(getContext());
}
private GestureDetector detector=new GestureDetector(new GestureDetector.SimpleOnGestureListener(){
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
//相对滑动:Y方向滑动多少距离,view就跟着滑动多少距离
//手指向上滑动
if(e2.getY()<e1.getY()&&getScrollY()>=0){
//滑到的y值超过0时,反弹回去
if(getScrollY()>0)scrollBy(0,-getScrollY());
//否则什么也不做
}
else if(getScrollY()<=0){
//如果手指向下滑动并且没有超过抽屉页的滑动范围,就滑动页面
if(!(e2.getY()>e1.getY()&&getScrollY()==-getVerticalHeight())){
scrollBy(0,(int)distanceY);
//手指向下滑动
if(e2.getY()>e1.getY())currentPos=getScrollY()/(getVerticalHeight()/4);
//手指向上滑动
else currentPos=getScrollY()/(4*getVerticalHeight()/5);
if(currentPos>0)currentPos=0;
if(currentPos < -1)currentPos=-1;
}
}
return super.onScroll(e1,e2,distanceX,distanceY);
}
});
@Override
public boolean onTouchEvent(MotionEvent event) {
//将event信息传给detector;
detector.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_UP:
scroller.startScroll(0,getScrollY(),0,getVerticalHeight()*currentPos-getScrollY());
invalidate();
break;
}
return true;
}
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()){
scrollTo(0,scroller.getCurrY());
postInvalidateDelayed(10);
}
}
private int getVerticalHeight(){
return getHeight()-getPaddingBottom()-getPaddingTop();
}
}
本篇到这里就结束,下一篇我们来解决该自定义Layout与RecyclerView滑动冲突的问题: