如何打造一个简单方便的LoadingLayout,添加头脚布

2020-03-14 08:10 来源:未知

美高梅网投平台 1

一个简单的切换加载中,空页面,出错页面和内容页面的布局。思路很简单自定义布局继承Framelayout,默认第一个子View为StateView,第二个子view为LoadingView,第三个子布局为EmptyView。默认界面加载完成后隐藏所有用户自定义布局。只显示以上其中一个布局。具体见图。github地址https://github.com/Hemumu/LoadingLayout

美高梅网投平台 2

demo.gif

主要就一个类就搞定了,很简单实用。新建一个类LoadingLayout继承Framelayout,重写它的构造方法。这里获取三个自定义参数,这三个参数主要是为了让用户自定义StateView,LoadingView,EmptyView。并通过LayoutInflater的inflater方法把它加入到LoadingLayout中。这里注意的是inflater第三个参数,true表示加载布局并加入第二参数的ViewGroup中,false表示只加载布局,如果第三个参数是false那么第二个参数就可以是null

public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoadingLayout, 0, 0);
        try {
            mStateView = a.getResourceId(R.styleable.LoadingLayout_stateView, R.layout.loadstate_layout);
            mLoadingView = a.getResourceId(R.styleable.LoadingLayout_loadingView, R.layout.loading_layout);
            mEmptyView = a.getResourceId(R.styleable.LoadingLayout_loadingView, R.layout.empty_layout);
            LayoutInflater inflater = LayoutInflater.from(getContext());
            inflater.inflate(mStateView, this, true);
            inflater.inflate(mLoadingView, this, true);
            inflater.inflate(mEmptyView, this, true);
        } finally {
            a.recycle();
        }
    }

这里我们重写View的onFinishInflate方法,onFinishInflate 会在当View中所有的子控件均被映射成xml后触发,换句话说就是布局文件被加载完成后回调的。在onFinishInflate方法中一般适合做初始化view,我们在此方法中我们隐藏所以子View

/**
* 布局加载完成后隐藏所有View
*/
@Override
protected void onFinishInflate() {
        super.onFinishInflate();
        for (int i = 0; i < getChildCount() - 1; i  ) {
            getChildAt(i).setVisibility(GONE);
        }
    }

接下来我们看三个状态View,其实很简单暴力的思路,因为我们在构造方法中依此加入了mStateView,mLoadingView,mEmptyView。那么第0个子view就是StateView第1个子view就是LoadingView第2个子view就是EmptyView。提供三个不同的方法来展示不同的状态

    /**
     * State View
     */
    public void showState() {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            //第0个子view为StateView
            if (i == 0) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }

    /**
     * Empty view
     */
    public void showEmpty() {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            //第2个子view为EmptyView
            if (i == 2) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }


    /**
     * Loading view
     */
    public void showLoading() {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            //第1个子view为LoadingView
            if (i == 1) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }

同时提供了默认的三种布局,StateView布局为下图所示

美高梅网投平台 3

state_view.png

提示扩展的展示方式,可以修改图片和文字,当然前面已经说过了你可以自定义任意见面的布局

    /**
     *
     * @param tips
     */
    public void showState(String tips) {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            if (i == 0) {
                child.setVisibility(VISIBLE);
                ((TextView) child.findViewById(R.id.load_state_tv)).setText(tips   "");
            } else {
                child.setVisibility(GONE);
            }
        }
    }

    /**
     * @param stateId
     * @param tips
     */
    public void showState(int stateId, String tips) {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            if (i == 0) {
                child.setVisibility(VISIBLE);
                ((ImageView) child.findViewById(R.id.load_state_img)).setImageResource(stateId);
                ((TextView) child.findViewById(R.id.load_state_tv)).setText(tips   "");
            } else {
                child.setVisibility(GONE);
            }
        }
    }

同样的LoadingView和EmptyView也有默认的布局样式(下图),用户也可以自定义,提供了相应的方法

美高梅网投平台 4

loading_view.png

美高梅网投平台 5

empty_view.png

   /**
     * Empty view
     *
     * @param text
     */
    public void showEmpty(String text) {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            if (i == 2) {
                child.setVisibility(VISIBLE);
                ((TextView) child.findViewById(R.id.empty_text)).setText(text   "");
            } else {
                child.setVisibility(GONE);
            }
        }
    }

   /**
     * Loadign view
     * @param text
     */
    public void showLoading(String text) {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            if (i == 1) {
                child.setVisibility(VISIBLE);
                ((TextView) child.findViewById(R.id.loading_text)).setText(text   "");
            } else {
                child.setVisibility(GONE);
            }
        }
    }

默认提供了为EmptyView和StateView设置点击事件的方法,点击的范围是整个view

    /**
     * 设置Empty点击事件
     * @param listener
     */
    public void setEmptyClickListener(final OnClickListener listener) {
        if( listener!=null )
            findViewById(R.id.state_retry2).setOnClickListener(listener);
    }

    /**
     * 设置State点击事件
     * @param listener
     */
    public void setStateClickListener( OnClickListener listener ){
        if(listener!=null)
            findViewById(R.id.state_retry).setOnClickListener(listener);
    }

通过调用setEmptyClickListener和setStateClickListener处理事件的回调,这里考虑到自定义的三种状态的View里面可能需要设置点击事件,或者设置文本和图片,提供了以下方法来修改自定义的状态view的文本图片和添加事件

 /**
     * 设置自定义布局的点击事件
     * @param resoureId
     * @param listener
     */
    public void setViewOncClickListener(int resoureId,OnClickListener listener) {
        findViewById(resoureId).setOnClickListener(listener);
    }

    /**
     * 设置自定义布局的view文本
     * @param resoureId
     * @param text
     */
    public void setViewText(int resoureId,String text){
        ((TextView)findViewById(resoureId)).setText(text);
    }

    /**
     * 设置自定义布局的image
     * @param resoureId
     * @param img
     */
    public void setViewImage(int resoureId,int img ){
        ((ImageView)findViewById(resoureId)).setImageResource(img);
    }

三种状态的view都是当前LoadingLayout的子view所以可以通过findViewById直接拿到。

最后一个方法就是showContent,很简单就是展示了除三种状态外的所有view。不过在写的时候因为LoadingLayout是继承FrameLayout所以建议用一个viewGroup包裹你的布局内容,比如LinearLayout,RelativeLayout。

   /**
     * 展示内容
     */
    public void showContent() {
        for (int i = 0; i < this.getChildCount(); i  ) {
            View child = this.getChildAt(i);
            if (i > 2 ) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }

本章属于第三种自定义控件,继承已有控件,扩展其功能。

android项目中经常需要从网络服务器端获取数据并显示到页面上,由于网络速度不稳定,客户端发起请求而服务端还未返回数据时,页面需要有加载中状态;如果请求失败,页面又需要显示为网络连接失败状态;如果这次请求的数据为空,页面还需要显示为暂无数据;只有服务端返回有效的数据时,页面才会正常显示。

Fastandrutils 是一套整理修改整合的android开发常用的工具类,常用的自定义view控件等。这样可以减少复制粘贴代码,从而减少重复代码,也不用为了一个常用的功能去谷歌百度,让代码更简洁,让开发更高效。同时希望您的添加完善,让android开发变得更简单。

使用

使用超级简单,直接看代码吧。布局文件

     <com.helin.loadinglayout.LoadingLayout
        android:id="@ id/loading_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@ id/list_view"
            android:layout_width="match_parent"
            android:dividerHeight="2dp"
            android:layout_height="match_parent">
        </ListView>

    </com.helin.loadinglayout.LoadingLayout>

直接用LoadingLayout包裹你自己的布局就行了,注意默认是什么都没有的,需要什么状态直接show出来就行了。showLoading,showState(),showEmpty(),showContent。也可以自定义三种状态。

        app:emptyView="@layout/empty_layout"
        app:loadingView="@layout/loading_layout"
        app:stateView="@layout/loadstate_layout"

通过LoadingLayout的setViewOncClickListener设置任何view的点击事件。setViewText设置TextView的文本信息,setViewImage设置ImageView的图片资源

//设置点击事件
mLoading.setViewOncClickListener(R.id.text_item, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this,"1111",Toast.LENGTH_SHORT).show();
            }
 });
//设置文本
mLoading.setViewText(R.id.text_item,"2222");
//设置图片资源
mLoading.setViewImage(R.id.img,R.drawable.test_img);

简单实用,有问题欢迎交流。the end!

注意:

这个需求在平时的开发过程中非常常见,因此我写了一个简单的多状态布局,包含这四种状态,方便在以后的项目中使用。这个loadingLayout的代码我全都上传到github上了,本来想发布到jCenter上,好给大家轻松通过gradle构建,后来又想了下,这个功能很简单,添加gradle依赖太重了,大家可以通过这篇文章自己实现,并配合自己的项目进行修改和扩展。

github地址,感兴趣的话,不妨点赞支持下个人博客

1.ListView的addHeaderView(view)/addFooterView(view)需要在ListView的setAdapter之前执行。

源码及demo地址:

大家可以去看看给我提意见啊,更欢迎star哈哈哈~~~

好的,啰嗦了一大堆,下面我们来正式开整,快速打造一个简单的loadingLayout。

大家应该很容易想到FrameLayout,将loading error empty content 这四种状态下的view放入一个FrameLayout中,提供方法根据状态来显示某一层view,隐藏其他层。

首先我们新建一个LoadingLayout类继承自FrameLayout,并定义mEmptyView mErrorView mLoadingView 三个View对象,定义两个onclickListener用于处理重新加载的逻辑。

public class LoadingLayout extends FrameLayout { private View mEmptyView, mErrorView, mLoadingView; private OnClickListener onErrorClickListener; private OnClickListener onEmptyClickListener; private LayoutInflater mLayoutInflater;} 

我们在它的构造方法中完成一些初始化的工作

public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoadingLayout, 0, 0); try { int emptyView = a.getResourceId(R.styleable.LoadingLayout_emptyView, R.layout.empty_view); int errorView = a.getResourceId(R.styleable.LoadingLayout_errorView, R.layout.error_view); int loadingView = a.getResourceId(R.styleable.LoadingLayout_loadingView, R.layout.loading_view); mLayoutInflater = LayoutInflater.from(getContext; mEmptyView = mLayoutInflater.inflate(emptyView, this, true); mErrorView = mLayoutInflater.inflate(errorView, this, true); mLoadingView = mLayoutInflater.inflate(loadingView, this, true); }finally { a.recycle(); } }

上面这段代码非常的简单,初始化了这个loadingView以后,在这个viewGroup中依次添加了emptyView errorView loadingView 这三个子view。由于LoadingLayout是继承自FrameLayout的,因此这三个子view是叠成3层显示的。

大家看到了我定义了emptyView,errorView,loadingView 三个属性,并且设置了默认值,所以我们要先在android app的styles文件中先定义好这三个属性,并且创建empty_view, error_view, loading_view三个默认的xml布局文件。

<declare-styleable name="LoadingLayout"> <attr name="loadingView" format="reference"/> <attr name="errorView" format="reference"/> <attr name="emptyView" format="reference"/></declare-styleable>
  • 默认的empty_view文件
<LinearLayout xmlns:andro android: android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/empty_view_bg" /> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="18dp" android:text="@string/no_data" android:textColor="@android:color/darker_gray" android:textSize="15sp" /></LinearLayout>
  • 默认的error_view文件
<LinearLayout xmlns:andro android: android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/error_view_bg"/> <TextView android: android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textSize="18sp" android:text="@string/network_error" android:textColor="@android:color/darker_gray"/> <Button android: android:layout_marginTop="10dp" android:layout_width="100dp" android:layout_height="32dp" android:text="@string/reload_data" android:textSize="15sp" android:textColor="@android:color/darker_gray" android:background="@drawable/corners_6dp"/></LinearLayout>
  • 默认的loading_view文件
<LinearLayout xmlns:andro android: android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:gravity="center" android:orientation="horizontal"> <LinearLayout android:layout_width="wrap_content" android:layout_height="50dp" android:alpha="100" android:background="@drawable/black_corners" android:gravity="center" android:orientation="horizontal" android:padding="5dp"> <ProgressBar android: android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" 正在加载…" android:textColor="@android:color/white" android:textSize="14sp" /> </LinearLayout></LinearLayout>

当View及其子View从xml文件中加载完成以后,会调用onFinishInflate方法,我们先将所有子view都隐藏。

 @Override protected void onFinishInflate() { super.onFinishInflate(); for (int i = 0; i < getChildCount() - 1; i  ) { getChildAt.setVisibility; } }

接下来就是重点了,我们根据不同的业务场景显示不同的view,其实非常简单,我们将loadingLayout的某一层布局显示出来,隐藏其他子布局就好了。由于我们是按照emptyView errorView loadingView contentView 这样的顺序添加的,因此可以通过view.getChildAt()方法,显示或隐藏指定布局。

  • 显示emptyView(emptyView为getChildAt
 public void showEmpty() { for (int i = 0; i < this.getChildCount { View child = this.getChildAt; if  { child.setVisibility; } else { child.setVisibility; } } }
  • 显示errorView(errorView为getChildAt
 public void showError() { for (int i = 0; i < this.getChildCount { View child = this.getChildAt; if  { child.setVisibility; } else { child.setVisibility; } } }
  • 显示loadingView(loadingView为getChildAt
 public void showLoading() { for (int i = 0; i < this.getChildCount { View child = this.getChildAt; if  { child.setVisibility; } else { child.setVisibility; } } }
  • 显示contentView(contentView为getChildAt
 public void showContent() { for (int i = 0; i < this.getChildCount { View child = this.getChildAt; if  { child.setVisibility; } else { child.setVisibility; } } }

在实际项目中,如果页面为空,可能业务上需要我们提供一个按钮点击跳转到首页? 购买页面? 其他指定页面?;如果因为网络原因加载失败,页面上一般会有一个重新加载按钮。这就是我在文章的开头说到的两个onclickListener的作用.

我们首先要提供两个set方法来设置onclickListener

 public LoadingLayout setOnEmptyClickListener(OnClickListener onEmptyClickListener) { this.onEmptyClickListener = onEmptyClickListener; return this; } public LoadingLayout setOnErrorClickListener(OnClickListener onErrorClickListener) { this.onErrorClickListener = onErrorClickListener; return this; }

然后再在onFinishInflate方法中,给按钮的点击事件实现这两个接口。

 findViewById(R.id.btn_empty).setOnClickListener(new OnClickListener() { @Override public void onClick { if (null != onEmptyClickListener) { onEmptyClickListener.onClick; } } }); findViewById(R.id.btn_error).setOnClickListener(new OnClickListener() { @Override public void onClick { if (null != onErrorClickListener) { onErrorClickListener.onClick; } } });

前面的工作做完,基本已经实现了需求,只是有时候我们不方便在xml中定义emptyView,又不想使用自定义的emptyView,所以我又写了一些扩展方法。

在java类中直接设置emptyView/errorView/loadingView。

 public LoadingLayout setEmptyView(@LayoutRes int layout) { removeView(getChildAt; mEmptyView = mLayoutInflater.inflate(layout, null, true); addView(mEmptyView, 0); onFinishInflate(); return this; } public LoadingLayout setErrorView(@LayoutRes int layout) { removeView(getChildAt; mErrorView = mLayoutInflater.inflate(layout, null, true); addView(mErrorView, 1); onFinishInflate(); return this; } public LoadingLayout setLoadingView(@LayoutRes int layout) { removeView(getChildAt; mLoadingView = mLayoutInflater.inflate(layout, null, true); addView(mLoadingView, 2); return this; }

修改自定义emptyView/errorView的文字

 public LoadingLayout setEmptyText(String text) { ( findViewById(R.id.btn_empty)).setText; return this; } public LoadingLayout setErrorText(String text) { ( findViewById(R.id.tv_error)).setText; return this; }

我在ids.xml文件中定义了三个id。

 <item name="btn_empty" type="id"/> <item name="btn_error" type="id"/> <item name="tv_error" type="id"/>

在自定义errorView中,一定要创建一个button并将id设置为btn_error,创建一个textView并将id设置为tv_error;同时在自定义emptyView时,要创建一个textView并将id设置为btn_epmty,否则会引发nullPointerException,切记切记!

下图就是在activity中最终的显示效果啦,忽略丑丑的布局,仓促写的。。。

美高梅网投平台 6simple_use.gif

使用方法:首先在activity或fragment的布局文件中插入loadingLayout,loadingLayout中包裹的就是contentView。(只允许包裹一个子 view,因此如果有多个view,需要用ScrollView等ViewGroup再包一层)

 <com.victor.loadinglayout.LoadingLayout android: android:layout_width="match_parent" android:layout_height="match_parent" app:errorView="@layout/error_view_demo2" app:emptyView="@layout/empty_view_demo2"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="@string/content"/> </com.victor.loadinglayout.LoadingLayout>

然后在java代码中,通过findViewById方法初始化view,并实现点击重试接口。使用showContent方法显示contView。

 loadingLayout = (LoadingLayout) findViewById(R.id.loading_layout); loadingLayout .setOnEmptyClickListener(new View.OnClickListener() { @Override public void onClick { loadingLayout.showLoading .setOnErrorClickListener(new View.OnClickListener() { @Override public void onClick { loadingLayout.showLoading .showContent();

感谢大家,撒花~~

以及,再次求star啊啊啊啊啊

打完广告,进入正题

2.在onTouchEvent中,如果返回值为true,说明当前事件被消费,返回值false,说明不消费该事件。

美高梅网投平台 7不多说,先上图


在开发过程中,最常的就是数据和界面间的交互,例如无数据时的界面展示,网络不通时的界面展示,对于这些不是正常的数据,我们都要做一些异常的展示界面,而不是一个空白界面,这样做一些异常界面的处理,用户体验上会更好点。源码地址

步骤:

  1. 显示加载界面
  2. 显示失败界面
  3. 可自定义空界面

1.自定义RefreshListView继承ListView,重写其构造方法。在构造方法中initView()

代码有点多,就贴些关键的

2.给ListView添加头布局,并对头布局做相应的处理,根据下拉的状态不同而更改头布局的UI

1)使用:addHeaderView(view);

2)根据更改HeaderView的paddingTop为-height隐藏头布局。

mHeaderView = View.inflate(getContext(), R.layout.layout_header_view,null);

mHeaderView.measure(0,0);//传入0,表示按照Xml中设置的宽高进行测量

mHeaderViewHeight =mHeaderView.getMeasuredHeight();

mHeaderView.setPadding(0,-mHeaderViewHeight ,0,0);

addHeaderView(mHeaderView);


  • FEmptyView 继承 FrameLayout
  • FEmptyView里的属性

获取头布局的高度需要注意:

此时是无法获取到头布局的高度的,因为一进入页面在oncreate方法中findviewById找到控件,此时,自定义控件的构造函数就已经调用,initView方法即调用,而自定义控件的渲染是在onCreate()方法之后。此时使用mHeaderView.getMeasuredHeight()或者mHeaderView.getHeight()方法拿到的高度值为0.

解决方法:在获取高度之前手动测量一下控件的宽高。

mHeaderView.measure(0,0);//传入0,表示按照Xml中设置的宽高进行测量,(父View已经测量过子View之后,填0是指按照XML中设置的宽高进行测量,一般是传childView.getLayoutParams.width来进行测量)

测量之后,使用mHeaderView.getMeasuredHeight()可以取得高度值。(getHeight是获得mHeaderView真实显示在界面上的高度)


3)下拉时,通过不停的更改-paddingTop值来使头布局慢慢显示出来。

a.监听ListView的触摸事件。重写onToucheEvent(),(不能删除return的super.onTouchEvent(ev),源码中ListView做了很多的处理,如果删除,则ListView无法滑动。)判断滑动距离来判断头布局的偏移量。

美高梅网投平台 8

分析

b.两种情况不会显示头布局,第一种是disY<0说明屏幕在向上滑动,第二种,第一条可见的条目position不为0,此时不需要显示头布局,那么就不需要设置padding值。

case MotionEvent.ACTION_DOWN:

//按下时获取按下Y坐标

    downY = ev.getY();

break;

case MotionEvent.ACTION_MOVE:

moveY = ev.getY();

    disY =moveY -downY;

    if(disY >0 && getFirstVisiblePosition() ==0){

mHeaderView.setPadding(0,(int)(-mHeaderViewHeight disY),0,0);

    }

break;

c.下拉结束后(即头布局完全显示),需要将箭头更改为向上,并且更改文字下拉刷新为松开刷新。当paddingTop >= 0时,更新UI。

定义几个int常量,记录头布局的状态:if paddingTop > = 0,说明头布局完全显示,那么此时头布局的状态应该为松开刷新,状态记为1:REFLEASE_REFRESH 

如果paddingTop<0,说明头布局未全部显示,此时头布局为下拉刷新,状态记为0.还有一种状态是正在刷新,状态记为0:PULL_TO_REFRESH

正在刷新:REFRESHING = 2;

代码方面,为了避免时时检测paddingTop(因为手指在屏幕上每一次微小的移动都会调用ACTION_MOVE这个状态下的代码,添加判断,只会在状态改变时进入执行if中更改动画、文字等代码),节约性能,可以把更改文字和动画的动作之前添加一个判断

if(paddingTop>=0&¤tState != REFLEASE_REFRESH){

.....//此处为更改动画和文字的代码:松开刷新状态,当前状态不为松开刷新状态时,才会进入这里

}else if(paddingTop < 0 && currentSate != PULL_TO_REFRESH){

//同上,当前状态不为下来刷新状态时,才会进来这里,如果状态已经是下来刷新状态,即使paddingTop<0,也不会进来这里。}

此处应该注意的是,如果在MOVE中添加了自己的事件,在MOVE中break前,添加return true;表示当前事件被我们处理并消费。

d.手指松开时的监听处理:

1.当手指松开时,头布局未完全显示,即paddingTop<0,还没有转化成松开刷新状态,即当前状态为下拉刷新PULL_TO_REFRESH,此时松开ListView应该是弹回去,即头布局隐藏,把头布局的paddingTop设置为-measureHeight即可。

2.当手机松开时,头布局已经完全显示,即paddingTop>=0,已经转化成松开刷新状态,即当前状态是REALEASE_REFRESH,此时松开,头布局paddingTop设置为0,并且上面的文字改变为正在刷新,iv隐藏(隐藏之前要清除动画,否则无法隐藏),pb显示。

3.如果状态为正在刷新中,控制用户不能拖拽,在ACTION_MOVE事件中,添加判断,如果是正在刷新,则执行父类对touch事件的处理。

if(current == REFRESHING){

//正在刷新时,不能往下拖拽,执行父类的touch事件的处理方式,当头布局显示完全时,不能拖拽

return super.onTouchEvent(ev);

}

效果图:

美高梅网投平台 9

下拉刷新效果图

 private LinearLayout setdataLay;//设置数据布局 private View emptyView;//空布局 private ImageView emptyImg;//空布局的ImageView private TextView emptyTv;//空布局的TextView private Button emptyBt;//空布局的Button private Context context;

  • 为了自定义界面可配置,添加自定义属性attrs

3.监听回调,当头布局状态为正在刷新时,需要告知外界,这时我正在下拉刷新,外界需要调用相关方法进行下拉刷新。

a.在RefreshListView中定义一个接口OnRefreshListener,接口中添加方法onRefresh();

public interface OnRefreshListener{

             void onRefresh();

}

b.在RefreshListView中添加方法setOnRefreshListener(OnRefreshListener listener),便于外界使用该接口,

public void setOnRefreshListener(OnRefreshListener listener){

this.listener = listener;

}

外界使用方法:此处在MAinActivity中

refreshListView.setOnRefreshListener(new OnRreshListener(){

onRefresh(){

//当RefreshListView的状态为正在刷新时,这个地方的方法会被调用

}});

c.在自定义View中,在适当的位置调用onRefresh()方法,比如在这个案例中,当用户手指抬起并且状态为正在刷新时,调用该方法,在自定义View中调用onRefresh(),实际上外界的(这里是MainActivity中的onRefresh被调用),此时可以把自定义View中的某些数据作为参数传递到界面上。

美高梅网投平台 10

d.下拉刷新:一般情况 ,下拉都是重新加载一遍数据。在这里模拟加载一条数据,首先添加到list中,再通知adapter更新数据即可。刷新完成之后需要通知RefreshListView,把头布局收起来,因此在RefreshView中定义一个方法,completedRefresh(),在方法中更改当前状态,隐藏头布局,更新UI。

美高梅网投平台 11

执行下拉刷新

美高梅网投平台 12

刷新完成之后更改布局

4.添加脚布局,上拉加载更多。

a.添加脚布局并隐藏

mFooterView = View.inflate(getContext(), R.layout.layout_foot_view, null);

mFooterView.measure(0,0);

mFooterViewHeight =mFooterView.getMeasuredHeight();

mFooterView.setPadding(0,-mFooterViewHeight,0,0);

美高梅网投平台,addFooterView(mFooterView);

b.添加onScrollListener,判断滑动状态,如果滑动状态为空闲状态,并且滑动到最后一个条目时,显示脚布局,跳到脚布局。

onScrollListener中的两个方法:onScrollStateChanged,当滑动状态改变时调用,滑动状态有三种分别是:

1.SCROLL_STATE_IDLE = 0 空闲状态,源码中的解释为:

The view is not scrolling. Note navigating the list using the trackball counts as being in the idle state since these transitions are not animated.

2.SCROLL_STATE_TOUCH_SCROLL = 1 用户在进行触摸滚动,源码中的解释为:

The user is scrolling using touch, and their finger is still on the screen

3.SCROLL_STATE_FLING = 2 滑翔状态 源码中的解释为:

The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop

用户滚动内容时,滚动状态变化的顺序为:0 --> 1--> 2 --> 0,即空闲-->用户开始滑动屏幕-->用户手指离开屏幕但屏幕仍在滑动-->滑翔结束回到空闲状态

此时需要在滚动状态重新回到空闲时判断是否滚动到最后一条,滚动到最后一条即显示脚布局:但是要注意,如果此时已经正在加载,用户往上拉的时候仍然会执行这几行代码,再一次进行加载更多的操作,为了避免这种情况发生,可以添加一个boolean类型的变量,进行标记和判断。

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

//当滚动状态改变时调用,当用户滑到最后一个并且滚动状态为空闲,getCount()得到的是adapter中的list中数据的总条数

       if(scrollState == SCROLL_STATE_IDLE && getLastVisiblePosition() == getCount() - 1&&!isLoadingMore){

               isLoadingMore= true;//标记为true,说明正在加载。

              //说明滚到最后一条,显示脚布局

               mFooterView.setPadding(0,0,0,0);

               setSelection(getCount());//显示最后一条

}

c.接口回调

1.在OnRefreshListener中添加方法,onLoadMore(),用于加载更多数据。

美高梅网投平台 13

添加onLoadMore方法

2.在RefreshListView中的脚布局出现时调用onLoadMore(),与下拉刷新相同,实际上onLoadMore()方法是在界面中使用接口时被调用。

美高梅网投平台 14

在界面中进行加载更多的处理。

美高梅网投平台 15

3.加载完成同样调用completedRefresh()方法,在方法中处理。判断是下拉刷新还是上拉加载更多。

美高梅网投平台 16

美高梅网投平台 17

完成效果图


扩展内容:

<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="EmptyLayout"> <attr name="empty_layout" format="reference" />//空界面的layout文件 <attr name="empty_imageView" format="reference" />//空界面imageView的id <attr name="empty_textView" format="reference" />//空界面textView的id <attr name="empty_button" format="reference" />//空界面button的id <attr name="include_layout" format="reference" />//数据展示界面的layout文件 </declare-styleable></resources>

1.自定义ProgressBar:

xml中添加该属性:indeterminateDrawable 无限循环的drawable

该属性的值为shape。

<rotate xmlns:android=""

android:fromDegrees="0"

    android:toDegrees="360"

    android:pivotX="50%"

    android:pivotY="50%"

    >

<!--旋转动画中可以包含shapeandroid:pivotX="50%" android:pivotY="50%" 相对于自己的中心位置android:pivotX="50%p" android:pivotY="50%p" 相对于父控件的中心位置-->

<shape

        android:shape="ring"

        android:innerRadius="@dimen/dp_20"

        android:thickness="@dimen/dp_5"

        android:useLevel="true"

        android:innerRadiusRatio="2.5"

        android:thicknessRatio="10"

      >

<gradient android:startColor="#88E93751"

            android:centerColor="#33E93751"

            android:endColor="#00000000"

            android:type="sweep"/>

   <!--在颜f色值前面添加00-f 00纯透明,ff不透明

         sweep:扫描-->

<!--

          内半径innerRadius

          厚度thickness

          内圆半径比 innerRadiusRatio="2.5" 内圆半径与容器宽高比

          圆环厚度比 thicknessRatio="10" 圆环厚度与容器宽高比

          -->

</shape>

</rotate>

  • FEmptyView 初始化界面布局

2.旋转动画

头布局中箭头的旋转动画

//向下翻转动画

public static void RotateDown(View view){

RotateAnimation animation = new RotateAnimation(

180f,0f,

Animation.RELATIVE_TO_SELF, 0.5f,

Animation.RELATIVE_TO_SELF, 0.5f);

animation.setDuration(500);

animation.setFillAfter(true);

view.startAnimation(animation);

}

//向上翻转动画

public static void RotateUp(View view){

RotateAnimation animation = new RotateAnimation(

0f, 180f,

Animation.RELATIVE_TO_SELF, 0.5f,

Animation.RELATIVE_TO_SELF, 0.5f);

animation.setDuration(500);

animation.setFillAfter(true);

view.startAnimation(animation);

}

 private void initView(TypedArray array) { int emptyViewId = array.getResourceId(FResourcesUtils.getStyleable("EmptyLayout_empty_layout"), FResourcesUtils.getLayoutResources("f_empty_layout")); int emptyImgId = array.getResourceId(FResourcesUtils.getStyleable("EmptyLayout_empty_imageView"), FResourcesUtils.getIdResources("empty_img")); int emptyTvId = array.getResourceId(FResourcesUtils.getStyleable("EmptyLayout_empty_textView"), FResourcesUtils.getIdResources("empty_tv")); int emptyBtId = array.getResourceId(FResourcesUtils.getStyleable("EmptyLayout_empty_button"), FResourcesUtils.getIdResources("empty_bt")); int dataViewId = array.getResourceId(FResourcesUtils.getStyleable("EmptyLayout_include_layout"), 0); //获取空布局View emptyView = View.inflate(context, emptyViewId, null); //获取空布局的imageView emptyImg = (ImageView) emptyView.findViewById(emptyImgId); emptyTv =  emptyView.findViewById(emptyTvId); emptyBt =  emptyView.findViewById(emptyBtId); setdataLay = new LinearLayout;//先添加一个LinearLayout setdataLay.setOrientation(LinearLayout.VERTICAL); setdataLay.setVisibility; if (dataViewId != 0) { addChildViewid(dataViewId);//把数据界面添加到LinearLayout里 } addView(setdataLay); addView(emptyView); }
  • 默认空界面布局 有一个ImageView,一个TextView,Button
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:gravity="center" android:orientation="vertical"> <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxHeight="200dp" android:maxWidth="200dp" /> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textSize="16sp" /> <Button android: android:layout_width="120dp" android:layout_height="40dp" android:layout_marginTop="6dp" /></LinearLayout>
  • FEmptyView 的一些方法void loadding()//显示加载界面void loaddingSuccess() 成功获取到数据把EmptyView隐藏void loaddingFail() 加载失败界面...

省略了比较多方法,详细看源码

  • ImagView 回调为了更好的展示ImagView 就写了个回调方法,自行解决ImagView的动画等
/** * ImagView 回调 */ public interface ImgCallBack { void setImg(ImageView img, int emptyType); }
  • emptyView 回调如果要做更深度自定义emptyView,这里也返回了emptyView,这样就可以获取所有emptyView的控件
/** * emptyView 回调 */ public interface ViewCallBack { void emptyViewCallBack(View view); }

第一种使用方法

  1. 在需要使用的xml添加
 <cn.hotapk.fastandrutils.widget.FEmptyView android: android:layout_width="match_parent" android:layout_height="match_parent" app:include_layout="@ layout/data_layout" />

FEmptyView 不能在xml下加子View只能这样添加数据界面

 app:include_layout="@ layout/data_layout"

在oncreat() 方法中

 empty_lay = (FEmptyView) findViewById(R.id.empty_lay); empty_lay.loadding("正在加载数据。。。"); empty_lay.loaddingFail("加载数据失败。。。", "点击刷新", new View.OnClickListener() { @Override public void onClick { loadding(); } }, new FEmptyView.ImgCallBack() { @Override public void setImg(ImageView img, int emptyType) { img.setBackgroundResource(R.mipmap.loaddingfail); if (img.getAnimation() != null) img.getAnimation().cancel;

第二种使用方法

<cn.hotapk.fastandrutils.widget.FEmptyView android: android:layout_width="match_parent" android:layout_height="match_parent"/>

FEmptyView 不能在xml下加子View只能在oncreat() 中这样添加数据界面

 empty_lay.addChildView(childView);

之后的使用第一种

第三种使用方法

可自定义emptyView

 <cn.hotapk.fastandrutils.widget.FEmptyView android: android:layout_width="match_parent" android:layout_height="match_parent" app:empty_button="@ id/custom_empty_bt"//自定义layout的button的id app:empty_imageView="@ id/custom_empty_img"//自定义layout的imageView的id app:empty_textView="@ id/custom_empty_tv"//自定义layout的textView的id app:empty_layout="@ layout/custom_empty_layout"//自定义的空 layout界面 app:include_layout="@ layout/data_layout" />//展示数据的layout

FEmptyView 同样不能在xml下加子View只能这样添加数据界面

 app:include_layout="@ layout/data_layout"

之后的使用同第一种

第四种使用方法

可深度自定义emptyView

 <cn.hotapk.fastandrutils.widget.FEmptyView android: android:layout_width="match_parent" android:layout_height="match_parent" app:empty_layout="@ layout/custom_empty_layout"//自定义的空 layout界面 app:include_layout="@ layout/data_layout" />//展示数据的layout

深度自定义的话代码中会返回自定义的emptyView

 public View getEmptyView() { return emptyView; }

可以findViewById获取自定义的控件之后的使用同第一种差不多

Android 自定义空数据提示界面 EmptyView 解说完毕

TAG标签:
版权声明:本文由美高梅网投平台发布于美高梅网投网址,转载请注明出处:如何打造一个简单方便的LoadingLayout,添加头脚布