Android RefreshLayout实现下拉刷新布局

项目中需要下拉刷新的功能,但是这个View不是ListView这类的控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。

于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦

为了省事,headView还是用了XListView的HeadView,省了很多事:)

下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:

1、ScrollView下只能有一个子控件View ,虽然在    Scroll下添加一个ViewGroup,然后讲headView动态添加进前面的ViewGroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!

2、  ScrollView内嵌ListView时会发生    冲突,还需要去重写ListView。于是放弃换个思路!

关于上面的原因1:动态添加headView进ScrollView的中GroupView中,可以在重写ScrollView的onViewAdded()方法,将初始化时解析的headView添加进子GroupView

@Override
public void onViewAdded(View child) {
  super.onViewAdded(child);
  //因为headView要在最上面,最先想到的就是Vertical的LinearLayout
  LinearLayout linearLayout = (LinearLayout) getChildAt(0);
  linearLayout.addView(view, 0);
}

换个思路,通过extends LinearLayout来实现吧!
先做准备工作,我们需要一个HeaderView以及要获取到HeaderView的高度,还有初始时Layout的高度

private void initView(Context context) {
   mHeaderView = new SRefreshHeader(context);
   mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);
   setOrientation(VERTICAL);
   addView(mHeaderView, 0);
   getHeaderViewHeight();
   getViewHeight();
 }

mHeaderView = new SRefreshHeader(context);
通过构造方法实例化HeaderView

mHeaderViewContent = (RelativeLayout)

mHeaderView.findViewById(R.id.slistview_header_content);

这是解析headerView内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mHeaderView来获取高度,点进构造方法里可以看到如下代码

// 初始情况,设置下拉刷新view高度为0
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null);
w(mContainer, lp);

如果直接获取mHeaderView的高度 那肯定是0
getHeaderViewHeight();
getViewHeight();
分别是获取HeaderView的高度和Layout的初始高度

/**
  * 获取headView高度
  */
  private void getHeaderViewHeight() {
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        mHeaderViewHeight = mHeaderViewContent.getHeight();
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
      }
    });
  } 

  /**
  * 获取SRefreshLayout当前实例的高度
  */
  private void getViewHeight() {
    ViewTreeObserver thisView = getViewTreeObserver();
    thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();
        SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
      }
    });
  }

准备工作完成了,接下来就是要成下拉操作了
到这里,肯定一下就想到了onTouchEvent()方法,是的!现在就开始在这里施工

实现下拉一共 会经历三个过程
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的时候,我们需要做的只是记录按下时候的坐标

switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        //记录起始高度
        mLastY = ev.getRawY();//记录按下时的Y坐标
        break;

然后就是ACTION_MOVE事件了,这里是最重要的,因为下拉时HeadView和Layout的高度变化都在这里进行

case MotionEvent.ACTION_MOVE:
       if (!isRefreashing)
         isRefreashing = true;
       final float deltaY = ev.getRawY() - mLastY;
       mLastY = ev.getRawY();
       updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离
       updateHeight();
       break;

里面的updateHeaderViewHeight和updateHeight分别是改变HeaderView的高度和Layout的高度

 private void updateHeight() {
    ViewGroup.LayoutParams lp = getLayoutParams();
    //更新当前layout实例高度为headerView高度加上最初的layout高度
    //如果不更新layout 会造成内容高度压缩 无法保持比例
    lp.height = (mHeight + mHeaderView.getVisiableHeight());
    setLayoutParams(lp);
  } 

  private void updateHeaderViewHeight(float space) {
//    if (space < 0)
//      space = 0;
//    int factHeight = (int) (space - mHeaderViewHeight);
    if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {
      //如果不处于刷新中同时如果高度
      if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {
        mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
      }
      if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {
        mHeaderView.setState(SRefreshHeader.STATE_READY);
      }
    }
    mHeaderView.setVisiableHeight((int) space
        + mHeaderView.getVisiableHeight());
  }

更新Header高度时,通过下拉的距离来判断是否到达刷新的距离,上面代码中我设定的是到达mHeaderView初始高度的两倍,就进入“释放刷新”的状态,如果没有达到则保持“下拉刷新”的状态
HeaderView中的状态一共设定了3个分别是

public final static int STATE_NORMAL = 0;//下拉刷新
 public final static int STATE_READY = 1;//释放刷新
 public final static int STATE_REFRESHING = 2;//刷新中

更新高度的方法headerView和layout都是相同的,就是原高度加上移动的距离重新赋给headerView或者layout

mHeaderView.setVisiableHeight((int) space 
               + mHeaderView.getVisiableHeight());

最后就是ACTION_UP事件了就是手指离开屏幕的时候,在这里我们需要根据headerView目前状态来决定headerView的最终状态!

case MotionEvent.ACTION_UP:
        //松开时
        //避免点击事件触发
        if (!isRefreashing)
          break;
        //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
        }
        //根据状态重置SrefreshLayout当前实例和headView高度
        resetHeadView(mHeaderView.getStatus());
        reset(mHeaderView.getStatus());
        mLastY = -1;//重置坐标
        break;

resetHeadView和reset分别是重置headerView高度和layout高度的方法

private void reset(int status) {
    ViewGroup.LayoutParams lp = getLayoutParams();
    switch (status) {
      case SRefreshHeader.STATE_REFRESHING:
        lp.height = mHeight + mHeaderViewHeight;
        break;
      case SRefreshHeader.STATE_NORMAL:
        lp.height = mHeight;
        break;
    }
    setLayoutParams(lp);
  } 

  private void resetHeadView(int status) {
    switch (status) {
      case SRefreshHeader.STATE_REFRESHING:
        mHeaderView.setVisiableHeight(mHeaderViewHeight);
        break;
      case SRefreshHeader.STATE_NORMAL:
        mHeaderView.setVisiableHeight(0);
        break;
    }
  }

实现方式也是一样的。根据状态来判断,如果是处于刷新中,那headerView应该正常显示,并且高度是初始的高度,如果处于NORMAL,也就是"下拉刷新"状态,那么说未触发刷新,重置时,headerView应该被隐藏,也就是高度重置为0

到这里下拉刷新操作也基本完成了,还需要加一个回调接口进行通知

interface OnRefreshListener {
    void onRefresh();
  }
case MotionEvent.ACTION_UP:
        //松开时
        //避免点击事件触发
        if (!isRefreashing)
          break;
        //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
          if (mOnRefreshListener != null)
            mOnRefreshListener.onRefresh();
        }
        //根据状态重置SrefreshLayout当前实例和headView高度
        resetHeadView(mHeaderView.getStatus());
        reset(mHeaderView.getStatus());
        mLastY = -1;//重置坐标
        break;

好,到这里就基本完成了,试试效果吧。咦,发现一个问题,嵌套ListView的时候为什么这个Layout不能执行下拉刷新!仔细想想应该是事件分发的问题,还需要处理一下事件的拦截!
关于事件拦截的处理,阅读了鸿洋大神写的viewgroup事件分发的博客和Android-Ultra-Pull-To-Refresh的部分源码,从中找到了解决办法:

@Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    AbsListView absListView = null;
    for (int n = 0; n < getChildCount(); n++) {
      if (getChildAt(n) instanceof AbsListView) {
        absListView = (ListView) getChildAt(n);
        Logs.v("查找到listView");
      }
    }
    if (absListView == null)
      return super.onInterceptTouchEvent(ev);
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mStartY = ev.getRawY();
        break;
      case MotionEvent.ACTION_MOVE:
        float space = ev.getRawY() - mStartY;
        Logs.v("space:" + space);
        if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {
          Logs.v("拦截成功");
          return true;
        } else {
          Logs.v("不拦截");
          return false;
        }
    }
    return super.onInterceptTouchEvent(ev);
  }

其中

if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space即移动的距离 canScrollVertically()是判断ListView能否在垂直方向上滚动,参数为负数时代表向上,为正数时代码向下滚动,最后一个就是ListView第一个可见的item的postion

加上上面的事件拦截处理,一个可以满足开头提到的需求的Viewgroup也就完成了!

下面贴上Layout的源码和HeaderView(直接使用的XlistView的HeaderView)的源码

public class SRefreshLayout extends LinearLayout {
  private SRefreshHeader mHeaderView;
  private RelativeLayout mHeaderViewContent;
  private boolean isRefreashing;
  private float mLastY = -1;//按下的起始高度
  private int mHeaderViewHeight;//headerView内容高度
  private int mHeight;//布局高度
  private float mStartY; 

  interface OnRefreshListener {
    void onRefresh();
  } 

  public OnRefreshListener mOnRefreshListener; 

  public SRefreshLayout(Context context) {
    super(context);
    initView(context);
  } 

  public SRefreshLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
  } 

  public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initView(context);
  } 

  private void initView(Context context) {
    mHeaderView = new SRefreshHeader(context);
    mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);
    setOrientation(VERTICAL);
    addView(mHeaderView, 0);
    getHeaderViewHeight();
    getViewHeight();
  } 

  /**
   * 获取headView高度
   */
  private void getHeaderViewHeight() {
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        mHeaderViewHeight = mHeaderViewContent.getHeight();
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);
      }
    });
  } 

  /**
   * 获取SRefreshLayout当前实例的高度
   */
  private void getViewHeight() {
    ViewTreeObserver thisView = getViewTreeObserver();
    thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();
        SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
      }
    });
  } 

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    AbsListView absListView = null;
    for (int n = 0; n < getChildCount(); n++) {
      if (getChildAt(n) instanceof AbsListView) {
        absListView = (ListView) getChildAt(n);
        Logs.v("查找到listView");
      }
    }
    if (absListView == null)
      return super.onInterceptTouchEvent(ev);
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mStartY = ev.getRawY();
        break;
      case MotionEvent.ACTION_MOVE:
        float space = ev.getRawY() - mStartY;
        Logs.v("space:" + space);
        if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {
          Logs.v("拦截成功");
          return true;
        } else {
          Logs.v("不拦截");
          return false;
        }
    }
    return super.onInterceptTouchEvent(ev);
  } 

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (mLastY == -1)
      mLastY = ev.getRawY();
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        //记录起始高度
        mLastY = ev.getRawY();//记录按下时的Y坐标
        break;
      //手指离开屏幕时
      case MotionEvent.ACTION_UP:
        //松开时
        //避免点击事件触发
        if (!isRefreashing)
          break;
        //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);
          if (mOnRefreshListener != null)
            mOnRefreshListener.onRefresh();
        }
        //根据状态重置SrefreshLayout当前实例和headView高度
        resetHeadView(mHeaderView.getStatus());
        reset(mHeaderView.getStatus());
        mLastY = -1;//重置坐标
        break;
      case MotionEvent.ACTION_MOVE:
        if (!isRefreashing)
          isRefreashing = true;
        final float deltaY = ev.getRawY() - mLastY;
        mLastY = ev.getRawY();
        updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离
        updateHeight();
        break;
    }
    return super.onTouchEvent(ev);
  } 

  private void reset(int status) {
    ViewGroup.LayoutParams lp = getLayoutParams();
    switch (status) {
      case SRefreshHeader.STATE_REFRESHING:
        lp.height = mHeight + mHeaderViewHeight;
        break;
      case SRefreshHeader.STATE_NORMAL:
        lp.height = mHeight;
        break;
    }
    setLayoutParams(lp);
  } 

  private void resetHeadView(int status) {
    switch (status) {
      case SRefreshHeader.STATE_REFRESHING:
        mHeaderView.setVisiableHeight(mHeaderViewHeight);
        break;
      case SRefreshHeader.STATE_NORMAL:
        mHeaderView.setVisiableHeight(0);
        break;
    }
  } 

  private void updateHeight() {
    ViewGroup.LayoutParams lp = getLayoutParams();
    //更新当前layout实例高度为headerView高度加上最初的layout高度
    //如果不更新layout 会造成内容高度压缩 无法保持比例
    lp.height = (mHeight + mHeaderView.getVisiableHeight());
    setLayoutParams(lp);
  } 

  private void updateHeaderViewHeight(float space) {
//    if (space < 0)
//      space = 0;
//    int factHeight = (int) (space - mHeaderViewHeight);
    if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {
      //如果不处于刷新中同时如果高度
      if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {
        mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
      }
      if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {
        mHeaderView.setState(SRefreshHeader.STATE_READY);
      }
    }
    mHeaderView.setVisiableHeight((int) space
        + mHeaderView.getVisiableHeight());
  } 

  public void stopRefresh() {
    if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) {
      mHeaderView.setState(SRefreshHeader.STATE_NORMAL);
      resetHeadView(SRefreshHeader.STATE_NORMAL);
      reset(SRefreshHeader.STATE_NORMAL);
    }
  } 

  public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
    this.mOnRefreshListener = onRefreshListener;
  }
}
public class SRefreshHeader extends LinearLayout {
  private LinearLayout mContainer;
  private int mState = STATE_NORMAL; 

  private Animation mRotateUpAnim;
  private Animation mRotateDownAnim; 

  private final int ROTATE_ANIM_DURATION = 500; 

  public final static int STATE_NORMAL = 0;//下拉刷新
  public final static int STATE_READY = 1;//释放刷新
  public final static int STATE_REFRESHING = 2;//刷新中
  private ImageView mHeadArrowImage;
  private TextView mHeadLastRefreashTimeTxt;
  private TextView mHeadHintTxt;
  private TextView mHeadLastRefreashTxt;
  private ProgressBar mRefreshingProgress; 

  public SRefreshHeader(Context context) {
    super(context);
    initView(context);
  } 

  /**
   * @param context
   * @param attrs
   */
  public SRefreshHeader(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
  } 

  private void initView(Context context) {
    // 初始情况,设置下拉刷新view高度为0
    LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
    mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null);
    addView(mContainer, lp);
    setGravity(Gravity.BOTTOM); 

    mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow);
    mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time);
    mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text);
    mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt);
    mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); 

    mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
        0.5f);
    mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
    mRotateUpAnim.setFillAfter(true);
    mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
        0.5f);
    mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
    mRotateDownAnim.setFillAfter(true);
  } 

  public void setState(int state) {
    if (state == mState) return; 

    if (state == STATE_REFRESHING) {  // 显示进度
      mHeadArrowImage.clearAnimation();
      mHeadArrowImage.setVisibility(View.INVISIBLE);
      mRefreshingProgress.setVisibility(View.VISIBLE);
    } else {  // 显示箭头图片
      mHeadArrowImage.setVisibility(View.VISIBLE);
      mRefreshingProgress.setVisibility(View.INVISIBLE);
    }
    switch (state) {
      case STATE_NORMAL:
        if (mState == STATE_READY) {
          mHeadArrowImage.startAnimation(mRotateDownAnim);
        }
        if (mState == STATE_REFRESHING) {
          mHeadArrowImage.clearAnimation();
        }
        mHeadHintTxt.setText("下拉刷新");
        break;
      case STATE_READY:
        if (mState != STATE_READY) {
          mHeadArrowImage.clearAnimation();
          mHeadArrowImage.startAnimation(mRotateUpAnim);
          mHeadHintTxt.setText("松开刷新");
        }
        break;
      case STATE_REFRESHING:
        mHeadHintTxt.setText("正在刷新");
        break;
      default:
    } 

    mState = state;
  } 

  public void setVisiableHeight(int height) {
    if (height < 0)
      height = 0;
    LayoutParams lp = (LayoutParams) mContainer
        .getLayoutParams();
    lp.height = height;
    mContainer.setLayoutParams(lp);
  } 

  public int getStatus() {
    return mState;
  } 

  public int getVisiableHeight() {
    return mContainer.getHeight();
  }
}

最后是布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:gravity="bottom"> 

  <RelativeLayout
    android:id="@+id/slistview_header_content"
    android:layout_width="match_parent"
    android:layout_height="60dp"> 

    <LinearLayout
      android:id="@+id/slistview_header_text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:gravity="center"
      android:orientation="vertical"> 

      <TextView
        android:id="@+id/slistview_header_hint_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下拉刷新" /> 

      <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"> 

        <TextView
          android:id="@+id/slistview_header_last_refreash_txt"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="上次刷新时间"
          android:textSize="12sp" /> 

        <TextView
          android:id="@+id/slistview_header_time"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textSize="12sp" />
      </LinearLayout>
    </LinearLayout> 

    <ProgressBar
      android:id="@+id/slistview_header_progressbar"
      android:layout_width="30dp"
      android:layout_height="30dp"
      android:layout_centerVertical="true"
      android:layout_toLeftOf="@id/slistview_header_text"
      android:visibility="invisible" /> 

    <ImageView
      android:id="@+id/slistview_header_arrow"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignLeft="@id/slistview_header_progressbar"
      android:layout_centerVertical="true"
      android:layout_toLeftOf="@id/slistview_header_text"
      android:src="@drawable/mmtlistview_arrow" /> 

  </RelativeLayout> 

</LinearLayout>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 探究Android中ListView复用导致布局错乱的解决方案

    首先来说一下具体的需求是什么样的: 需求如图所示,这里面有ABCD四个选项的题目,当点击A选项,如果A是正确的答案,则变成对勾的图案,如果是错误答案,则变成错误的图案,这里当时在写的时候觉得很简单,只要是在点击的时候判断我点击的选项与正确答案是否一样,是一样就将图片换成正确的样式,如果不一样就换成错误的样式,于是我便写了下面的代码(只贴出了核心Adapter中的代码) package com.fizzer.anbangproject_dahuo_test.Adapter; import andr

  • Android 动态改变布局实例详解

    Android 动态改变布局                最近项目需求,动态的改变布局,为了增加客户体验,尤其是在输入框出现小键盘的时候,为了避免小键盘遮挡APP内容就需要动态改变布局: 先看下实现效果图: 其实是一个软件的登录界面,初始是第一个图的样子,当软键盘弹出后变为第二个图的样子,因为登录界面有用户名.密码.登录按钮,不这样的话软键盘弹出后会遮住登录按钮(其实之前的实现放到了ScrollView里面,监听软键盘弹出后滚动到底部,软键盘隐藏后滚动到顶部,也是可以的). 最简单的方法就是多

  • Android ListView自动显示隐藏布局的实现方法

    借助View的OnTouchListener接口来监听listView的滑动,通过比较与上次坐标的大小,判断滑动方向,并通过滑动方向来判断是否需显示或者隐藏对应的布局,并且带有动画效果. 1.自动显示隐藏Toolbar 首先给listView增加一个HeaderView,避免第一个Item被Toolbar遮挡. View header=new View(this); header.setLayoutParams(new AbsListView.LayoutParams( AbsListView.

  • Android 五大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(TableLayout):按照行列方式布局组件. 相对布局(RelativeLayout):相对其它组件的布局方式.  绝对布局(AbsoluteLayout):按照绝对坐标来布局组件.  1. 线性布局 线性布局是Android开发中最常见的一种布局方式,它是按照垂直或者水平方向来布局,通过"androi

  • Android实现的ListView分组布局改进示例

    本文实例讲述了Android实现的ListView分组布局改进方法.分享给大家供大家参考,具体如下: 由于是在网上转载的一篇文章,在这里就不多说废话了,首先看一下最终的效果图: 然后是实现该ListView布局的主要代码: 1.程序主界面 SeparateListView.java package whu.iss.wuxianglong; import java.util.ArrayList; import java.util.List; import android.app.Activity;

  • Android实现气泡布局/弹窗效果 气泡尖角方向及偏移量可控

    Android 自定义布局实现气泡弹窗,可控制气泡尖角方向及偏移量. 效果图 实现 首先自定义一个气泡布局. /** * 气泡布局 */ public class BubbleRelativeLayout extends RelativeLayout { /** * 气泡尖角方向 */ public enum BubbleLegOrientation { TOP, LEFT, RIGHT, BOTTOM, NONE } public static int PADDING = 30; public

  • Android动画效果之自定义ViewGroup添加布局动画(五)

    前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通过对自定义图片选择控件设置动画为例来学习布局动画. 自定义一个显示多行图片的ViewGroup: 这里不再对自定义控件做解说,想了解的可以看下以下几篇文章  •Android自定义控件之基本原理(一)  •Android自定义控件之自定义属性(二)  •Android自定义控件之自定义组合控件(三)

  • Android布局实现圆角边框效果

    首先,在res下面新建一个文件夹drawable,在drawable下面新建三个xml文件:shape_corner_down.xml.shape_corner_up.xml和shape_corner.xml,分别是下面两个角是圆角边框,上面两个角是圆角边框,四个角全部是圆角边框. shape_corner_down.xml: <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android=&

  • Android RecyclerView加载不同布局简单实现

    前言 关于RecyclerView的使用这里就不在赘述了,相信网上一搜一大把(本人之前的文章也有简单的使用介绍),这次我们讲的是RecyclerView在使用的过程中,有时候会根据不同的位置加载不同的布局的简单实现,这里只是起到抛砖引玉的作用 效果图 设计思想  •重写RecyclerView.Adapter的getItemViewType(int position), 在此方法中根据不同的position,设置不同的ViewType  •编写具体的RecyclerView.ViewHolder

  • Android RefreshLayout实现下拉刷新布局

    项目中需要下拉刷新的功能,但是这个View不是ListView这类的控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个. 于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦 为了省事,headView还是用了XListView的HeadView,省了很多事:) 下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为

  • Android中Listview下拉刷新和上拉加载更多的多种实现方案

    listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局 android系统为listview提供了addfootview和addheadview两个API.这样可以直接自定义一个View,以添加视图的形式实现下来刷新和上拉加载. 实现步骤    1.创建一个类继承ListView:class PullToRefreshListView extends ListView: 2.在构造方法中添加HeadView:addHeaderVi

  • Android中ListView下拉刷新的实现方法实例分析

    本文实例讲述了Android中ListView下拉刷新的实现方法.分享给大家供大家参考,具体如下: ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: package net.loonggg.listview; import java.util.Date; import android.content.Context; import android.util.

  • Android RecyclerView设置下拉刷新的实现方法

    Android RecyclerView设置下拉刷新的实现方法 1 集成 SwipeRefreshLayout 1.1 xml布局文件中使用 <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh" android:layout_width = "match_parent" android:layout_height = "match_parent" &g

  • Android实现RecyclerView下拉刷新效果

    本文为大家分享了Android实现RecyclerView下拉刷新效果的具体代码,供大家参考,具体内容如下 思路 RealPullRefreshView继承了一个LinearLayout 里面放置了一个刷新头布局,将其margin_top设置为负的刷新头的高度的 再添加一个RecyclerView 触摸事件分发机制,当在特定条件下让RealPullRefreshView拦截触摸事件,否则的话,不拦截,让RecyclerView自己去处理触摸事件 在手指下拉时,定义好不同的状态STATE,在不同状

  • Android中ListView下拉刷新的实现代码

    Android中ListView下拉刷新 实现效果图: ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: package net.loonggg.listview; import java.util.Date; import android.content.Context; import android.util.AttributeSet; import a

  • android 有阻尼下拉刷新列表的实现方法

    本文将会介绍有阻尼下拉刷新列表的实现,先来看看效果预览: 这是下拉状态: 这是下拉松开手指后listView回滚到刷新状态时的样子: 1. 如何调用 虽然效果图看起来样子不太好看,主要是因为那个蓝色的背景对不对,没关系,这只是一个背景而已,在了解了我们这个下拉刷新列表的实现之后,你就可以很轻松地修改这个背景,从而实现你想要的UI效果!话不多说,下面我们先来讲讲这个下拉刷新列表是如何使用的,这也是我们编写代码所要实现的目标. final PullToRefreshListView eListVie

  • Android 实现的下拉刷新效果

    下面是自己实现的效果: 1.分析 可以将动画分解成: 睁眼毛驴绕着中心地球旋转,并且在到达地球中心时,切换为闭眼毛驴,最后发射出去 地球自我旋转,随着下拉而缓缓上升,达到半径距离后停止上升 一颗上下来回移动的卫星 2.实现 (1)下载赶集app,然后将其后缀名改为zip解压获取我们需要的资源图片: (2) 我们先实现卫星的上下移动 核心代码: @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Matrix

  • Android Scroller及下拉刷新组件原理解析

    Android事件拦截机制 Android中事件的传递和拦截和View树结构是相关联的,在View树中,分为叶子节点和普通节点,普通节点有子节点只能是ViewGroup,叶子节点可以是View或者ViewGroup.Android和事件分发拦截相关的方法有 dispatchTouchEvent(MotionEvent ev) 事件分发相关的方法,沿着View树将一个用户的触摸事件向下分发. onInterceptTouchEvent(MotionEvent ev) 在dispatchTouchE

  • Android RecyclerView实现下拉刷新和上拉加载更多

    使用官方的刷新控件SwipeRefreshLayout来实现下拉刷新,当RecyclerView滑到底部实现下拉加载(进度条效果用RecyclerView加载一个布局实现) 需要完成控件的下拉监听和上拉监听,其中,下拉监听通过SwipRefreshLayout的setOnRefreshListener()方法监听,而上拉刷新,需要通过监听列表的滚动,当列表滚动到底部时触发事件,具体代码如下 主布局 <?xml version="1.0" encoding="utf-8&

随机推荐