前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 自定义底部上拉控件的实现方法

Android 自定义底部上拉控件的实现方法

作者头像
砸漏
发布2020-11-01 14:32:22
1.4K0
发布2020-11-01 14:32:22
举报
文章被收录于专栏:恩蓝脚本

前言

又到了新的一月,今天提供一个Android自定义底部上拉布局的实现,起因是自己在项目中需要实现这样一个控件,干脆自己写一个练练手。

写完了觉得能想到的需求都基本有了(可能会有其它需求,不过基本上改吧改吧就行了),又花了一点时间直接放到了Github上托管,希望能给您一些参考价值:

SlideBottomLayout-Android 简单易上手的Android底部上拉控件

先看一下实现效果:

分析一下这种控件的基本需求有以下几种:

1.有一个部分是能够作为把手(就是图中的handle,)进行拖拽的,这部分高度是暴露在界面中的 – 需要实现:Handle按钮

* 特殊需求特殊分析,比如让这个Handle透明实现无Handle的效果

2.底部上啦布局是有一定高度限制的,不一定覆盖设备的整个屏幕 – 需要自定义最大高度

3.当从底部上拉一点点时抬手,布局缩回,若超过一定高度,自动弹到最高,隐藏同理 – 需要自定义自动到达顶部/隐藏的阈值

直接使用

直接使用也很简单,笔者进行了简单的封装,以供参考:

1. 在Project的build.gradle文件中添加:

代码语言:javascript
复制
allprojects {
 repositories {
  ...
  maven { url 'https://jitpack.io' }
 }
}

2.在Module的build.gradle文件中添加:

代码语言:javascript
复制
dependencies {
   compile 'com.github.qingmei2:SlideBottomLayout-Android:1.2.3'
}

3.Add view in your layout:

需要注意的是:为了简单实现,笔者偷了个懒,设定为该布局下只能有一个直接的子View(类似ScrollView)

因此如果您添加需要一个布局,请在外面嵌套一个ViewGroup:

代码语言:javascript
复制
<com.qingmei2.library.SlideBottomLayout
    android:id="@+id/slideLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginTop="200dp"
    app:handler_height="50dp" 
    <!--app:handler_height:该属性就是您要暴露出来Handle的高度,详见下方的TextView(id=handle)-- 
    <!--Just one child-- 
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical" 
      <TextView
        android:id="@+id/handle"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#d95858"
        android:gravity="center"
        android:text="handle"
        android:textColor="@android:color/white"
        android:textSize="16sp" / 
      <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
      </android.support.v7.widget.RecyclerView 
    </LinearLayout 
  </com.qingmei2.library.SlideBottomLayout 

实现步骤

具体代码如下,其中上述需求的设置方式都已经在代码中声明:

先看下属性声明:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"? 
<resources 
  <declare-styleable name="SlideBottomLayout" 
    <attr name="handler_height" format="dimension" </attr 
  </declare-styleable 
</resources 
代码语言:javascript
复制
public class SlideBottomLayout extends LinearLayout {
public void setShortSlideListener(ShortSlideListener listener) {
this.shortSlideListener = listener;
}
private ShortSlideListener shortSlideListener;
/**
* The {@link MotionEvent#ACTION_DOWN} gesture location.
*/
private int downY;
/**
* The {@link MotionEvent#ACTION_MOVE} gesture location.
*/
private int moveY;
/**
* the value of moved distance by the gesture. When the value was modified and not exceed
* the {@link #movedMaxDis}, then make this ViewGroup move.
*/
private int movedDis;
/**
* The max distance that the {@link SlideBottomLayout} can scroll to, it used to compare with the
* {@link #downY}, determine whether it can slide by the gesture.
*/
private int movedMaxDis;
/**
* ChildView of the {@link SlideBottomLayout}, you can set a Layout such as the {@link LinearLayout}、
* {@link android.widget.RelativeLayout} ect.
* We set the rules that {@link SlideBottomLayout} just can have one child-view, or else get a
* {@link RuntimeException} at {@link #onFinishInflate()}
*/
private View childView;
/**
* The control {@link SlideBottomLayout} automatically switches the threshold of the state. if
* this ViewGroup moved distance more than {@link #movedMaxDis} * it, switch the state of
* {@link #arriveTop} right now.
* </p 
* See the {@link #touchActionUp(float)}.
*/
private float hideWeight = 0.25f;
//3.注意,这个接口用来设置「需要自定义自动到达顶部/隐藏的阈值」
public void setHideWeight(float hideWeight) {
if (hideWeight <= 0 || hideWeight   1)
throw new IllegalArgumentException("hideWeight should belong (0f,1f]");
this.hideWeight = hideWeight;
}
private Scroller mScroller;
/**
* It means the {@link #childView} is arriving the top of parent or else.
*/
private boolean arriveTop = false;
/**
* the {@link #childView} Initially visible height
*/
private float visibilityHeight;
//1.初始化Handle显示高度,建议您在xml中设置对应属性来实现该效果
public void setVisibilityHeight(float visibilityHeight) {
this.visibilityHeight = visibilityHeight;
}
public SlideBottomLayout(@NonNull Context context) {
super(context);
}
public SlideBottomLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
}
public SlideBottomLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}
/**
* Get the config from {@link R.styleable}, then init other configrations{@link #initConfig(Context)}.
*
* @param context the {@link Context}
* @param attrs  the configs in layout attrs.
*/
private void initAttrs(Context context, AttributeSet attrs) {
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlideBottomLayout);
visibilityHeight = ta.getDimension(R.styleable.SlideBottomLayout_handler_height, 0);
ta.recycle();
initConfig(context);
}
private void initConfig(Context context) {
if (mScroller == null)
mScroller = new Scroller(context);
this.setBackgroundColor(Color.TRANSPARENT);
}
/**
* It start a judgement for ensure the child-view be unique in this method,then assgin it
* to {{@link #childView}.
* this method will be called before the {@link #onMeasure(int, int)}
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() == 0 || getChildAt(0) == null) {
throw new RuntimeException("there have no child-View in the SlideBottomLayout!");
}
if (getChildCount()   1) {
throw new RuntimeException("there just alow one child-View in the SlideBottomLayout!");
}
childView = getChildAt(0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
movedMaxDis = (int) (childView.getMeasuredHeight() - visibilityHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
childView.layout(0, movedMaxDis, childView.getMeasuredWidth(), childView.getMeasuredHeight() + movedMaxDis);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final float dy = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (touchActionDown(dy))
return true;
break;
case MotionEvent.ACTION_MOVE:
if (touchActionMove(dy))
return true;
break;
case MotionEvent.ACTION_UP:
if (touchActionUp(dy))
return true;
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller == null)
mScroller = new Scroller(getContext());
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
/**
* When the touch event is {@link MotionEvent#ACTION_UP},
* then judge the state of view group and control the {@link Scroller} to scroll.
* <p 
* In this ViewGroup, we set the rules that is if this scroll gesture's move distance
* more than {@link #movedMaxDis} * {@link #hideWeight}(default hideWeight value is 1/4 heights
* of this ViewGroup), then call {@link #hide()} or {@link #show()} right now. which method will
* be call depends on {@link #arriveTop}.
* <p
* if the scroll gesture's move distance don't reach the goal value, then call the
* {@link ShortSlideListener#onShortSlide(float)} if you call {@link #setShortSlideListener(ShortSlideListener)}
* init this ViewGroup. else will call {@link #hide()}.
*
* @param eventY The location of trigger
* @return Be used to determine consume this event or else.
*/
public boolean touchActionUp(float eventY) {
if (movedDis   movedMaxDis * hideWeight) {
switchVisible();
} else {
if (shortSlideListener != null) {
shortSlideListener.onShortSlide(eventY);
} else {
hide();
}
}
return true;
}
/**
* When the touch event is {@link MotionEvent#ACTION_MOVE},
* then judge the state of view group and control the {@link Scroller} to scroll.
* <p 
* In this ViewGroup, we set the rules that is if this scroll gesture's move distance
* more than {@link #movedMaxDis} * {@link #hideWeight}(default hideWeight value is 1/4 heights of this ViewGroup),
* then call {@link #hide()} or {@link #show()} right now.
* <p 
*
* @param eventY The location of trigger
* @return Be used to determine consume this event or else.
*/
public boolean touchActionMove(float eventY) {
moveY = (int) eventY;
//the dy is sum of the move distance, the value   0 means scroll up, the value < 0 means scroll down.
final int dy = downY - moveY;
if (dy   0) {        //scroll up
movedDis += dy;
if (movedDis   movedMaxDis)
movedDis = movedMaxDis;
if (movedDis < movedMaxDis) {
scrollBy(0, dy);
downY = moveY;
return true;
}
} else {        //scroll down
movedDis += dy;
if (movedDis < 0) movedDis = 0;
if (movedDis   0) {
scrollBy(0, dy);
}
downY = moveY;
return true;
}
return false;
}
/**
* When the touch event is {@link MotionEvent#ACTION_DOWN},
* Record the location of this action.
*
* @param eventY The location of trigger
* @return Be used to determine consume this event or else.
*/
public boolean touchActionDown(float eventY) {
downY = (int) eventY;
//Whether custom this gesture.
if (!arriveTop && downY < movedMaxDis) {
return false;
} else
return true;
}
/**
* the extand method for showing {@link SlideBottomLayout}
*/
public void show() {
scroll2TopImmediate();
}
/**
* the extand method for hiding {@link SlideBottomLayout}
*/
public void hide() {
scroll2BottomImmediate();
}
/**
* @return The ViewGroup is arrive top or else.
*/
public boolean switchVisible() {
if (arriveTop())
hide();
else
show();
return arriveTop();
}
public boolean arriveTop() {
return this.arriveTop;
}
public void scroll2TopImmediate() {
mScroller.startScroll(0, getScrollY(), 0, (movedMaxDis - getScrollY()));
invalidate();
movedDis = movedMaxDis;
arriveTop = true;
}
public void scroll2BottomImmediate() {
mScroller.startScroll(0, getScrollY(), 0, -getScrollY());
postInvalidate();
movedDis = 0;
arriveTop = false;
}
}

注释也比较明了,如果有疑问,详细请参照SlideBottomLayout-Android 简单易上手的Android底部上拉控件

里面有相对详细的使用说明,此外,如果还有一些需求,您可以在issue中提出,提前感谢!

以上这篇Android 自定义底部上拉控件的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档