首页 > 系统 > Android > 正文

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

2019-10-22 18:16:40
字体:
来源:转载
供稿:网友

前言

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

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

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

先看一下实现效果:

Android,自定义,底部,上拉,控件

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

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

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

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

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

直接使用

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

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

allprojects { repositories {  ...  maven { url 'https://jitpack.io' } }}

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

dependencies {   compile 'com.github.qingmei2:SlideBottomLayout-Android:1.2.3'}

3.Add view in your layout:

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

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

<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>

实现步骤

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

先看下属性声明:

<?xml version="1.0" encoding="utf-8"?><resources>  <declare-styleable name="SlideBottomLayout">    <attr name="handler_height" format="dimension"></attr>  </declare-styleable></resources>
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 自定义底部上拉控件的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持VEVB武林网。


注:相关教程知识阅读请移步到Android开发频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表