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

前言

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

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

SlideBottomLayout-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 自定义底部上拉控件的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Android自定义控件实现底部菜单(上)
  • Android自定义控件实现底部菜单(下)
  • Android自定义控件实现随手指移动的小球
  • Android自定义控件实现滑动开关效果
  • Android编程实现自定义控件的方法示例
(0)

相关推荐

  • Android自定义控件实现随手指移动的小球

    一个关于自定义控件的小Demo,随着手指移动的小球. 先看下效果图: 实现代码如下: 1.自定义控件类 package com.dc.customview.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import

  • Android编程实现自定义控件的方法示例

    本文实例讲述了Android编程实现自定义控件的方法.分享给大家供大家参考,具体如下: 很多时候Android常用的控件不能满足我们的需求,那么我们就需要自定义一个控件了.今天做了一个自定义控件的实例,来分享下. 首先定义一个layout实现按钮内部布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.c

  • Android自定义控件实现底部菜单(上)

    今天我们封装一个底部的菜单栏,这个大多数的应用都会用到,因此我们来自定义,方便以后项目的使用. 该控件的实现将分上下篇来介绍,先来看一个菜单栏的子控件–MenuItemM,这个控件有什么用呢?我们来看下一些主流app上的一些控件,如: 以上三张图片分别来自微信,今日头条和去哪儿,接下来我们将看到如何通过一个控件来实现不同的效果. 首先看下我写的一个deme 可以看到标题栏的消息控件,以及底部三个菜单项都是通过MenuItemM来实现的 这里面只是演示菜单栏的子控件,我们将在下一篇博客中完成底部菜

  • Android自定义控件实现滑动开关效果

    自定义开关控件 Android自定义控件一般有三种方式 1.继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑. 2.继承ViewGroup,这类自定义控件是可以往自己的布局里面添加其他的子控件的. 3.继承View,这类自定义控件没有跟原生的控件有太多的相似的地方,也不需要在自己的肚子里添加其他的子控件. ToggleView自定义开关控件表征上没有跟Android原生的控件有什么相似的地方,而且在滑动的效果上也没有沿袭Android原生的地方,所以我们的自定义

  • Android自定义控件实现底部菜单(下)

    在app中经常会用到底部菜单的控件,每次都需要写好多代码,今天我们用到了前几篇博客里的控件来进一步封装底部菜单.先看效果图: 主要包括以下功能: 1 设置icon以及点击之后的icon 2 设置文字 3 设置文字颜色以及点击之后的文字颜色 4 设置未读数量.更多以及new 我们先看如何使用,然后再看如何实现的 1 在布局文件中引用MenuM <com.landptf.view.MenuM android:id="@+id/mm_bottom" android:layout_wid

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

    前言 又到了新的一月,今天提供一个Android自定义底部上拉布局的实现,起因是自己在项目中需要实现这样一个控件,干脆自己写一个练练手. 写完了觉得能想到的需求都基本有了(可能会有其它需求,不过基本上改吧改吧就行了),又花了一点时间直接放到了Github上托管,希望能给您一些参考价值: SlideBottomLayout-Android 简单易上手的Android底部上拉控件 先看一下实现效果: 分析一下这种控件的基本需求有以下几种: 1.有一个部分是能够作为把手(就是图中的handle,)进行

  • android自定义WaveView水波纹控件

    本文实例为大家分享了android自定义WaveView水波纹控件的使用方法,供大家参考,具体内容如下 Github Repository and libaray WaveView水波纹控件 首先看下演示demo demo中可以看到不同高度,不同速度,不同幅度的水波纹:你可以通过view的参数直接控制view的表现形式. 引入你的工程 在项目的根目录下的build.gradle文件中添加如下代码: allprojects { repositories { ... maven { url 'htt

  • android自定义圆形倒计时显示控件

    本文实例为大家分享了android自定义圆形倒计时显示控件的具体代码,供大家参考,具体内容如下 先上效果图 - 倒计时结束 代码块 attr.xml 控件需要用到的属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CountDownView"> <!--颜色--> <attr name

  • Android自定义view实现输入控件

    本文实例为大家分享了Android自定义view实现输入控件的具体代码,供大家参考,具体内容如下 网络上大部分的输入控件都是多个EditText组合而成,本例中采用的是: 单个EditText作为输入的捕捉控件 多个ImageView的子类作为显示的控件,绘制EditText中的数据 如上图: 输入前和输入后输入框需要发生响应的改变 点击自定义控件要弹出软键盘 EditText数据捕捉,以及EditView不能操作(如果可以操作,数据处理会混乱) 输完后会得到相应的提示 ImageView的子类

  • Android 自定义日期段选择控件功能(开始时间-结束时间)

    开发中碰到个需求,需要在一个空间中选择完成开始和结束时间.实现的过程走的是程序员开发的老路子,找到轮子后自己改吧改吧就成了. 当时做的时候有几个需求:1.当天为最大的结束日期,2.最大选择范围1年,3.开始时间和结束时间可以为同一天.如有其他需求实现,可以参考代码改进一下.先上效果图: 视频点击后的虚影是屏幕录制的原因.实现步骤:(如有缺失什么资源,请告知.开始时间和结束时间显示自己布局内添加就可以) 1.自定义控件属性 <declare-styleable name="MyCalenda

  • Android自定义view实现倒计时控件

    本文实例为大家分享了Android自定义view实现倒计时控件的具体代码,供大家参考,具体内容如下 直接上代码 自定义TextView 文字展示 public class StrokeTextView extends TextView { private TextView borderText = null;///用于描边的TextView private Context mContext; public StrokeTextView(Context context) { super(conte

  • Android自定义View之组合控件实现类似电商app顶部栏

    本文实例为大家分享了Android自定义View之组合控件,仿电商app顶部栏的相关代码,供大家参考,具体内容如下 效果图: 分析:左右两边可以是TextView和Button,设置drawableTop即可,中间的看着像是EditText,但是用过淘宝天猫等类似app的话会发现点击搜索不是在当前Activit进行搜索的,是跳转到另外的页面进行的,所以用TextView然后设置背景即可. 实现流程 参数列表: 设置属性文件:values下建立attrs.xml文件,添加需要自定义的属性. <?x

  • Android自定义顶部导航栏控件实例代码

    下面一段代码给大家介绍了android 自定义顶部导航栏控件功能,具体代码如下所示: class HeaderBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { //重写构造方法 在java里面 我们一般是重写三个构造方法//在kotlin中 我们可以使用

  • JS禁用页面上所有控件的实现方法(附demo源码下载)

    本文实例讲述了JS禁用页面上所有控件的实现方法.分享给大家供大家参考,具体如下: 利用页面元素的特征,可以捕捉到所有元素. function DisableElements(container,blnHidenButton) { if (!container) return; var aEle; if (navigator.appName =="Microsoft Internet Explorer") //IE { for (var i=0;i<container.all.le

  • Android自定义滑动接听电话控件组实例

    本文根据组件开发思想,首先介绍android自定义控件,然后将自定义的控件封装为jar包.最为实现滑动接听电话控件组. 一.目录结构 二.运行效果 三.代码实现 首先,自定义一个类IncomingPhone继承RelativeLayout public IncomingPhone(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; TextView textView = new Tex

随机推荐