如何使用 RecyclerView 显示空视图?
How to show an empty view with a RecyclerView?
我习惯在layout文件里面放一个特殊的view作为described in the ListActivity
documentation在没有数据的时候显示。此视图的 ID 为 "android:id/empty"
.
<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_data" />
我想知道如何使用新的 RecyclerView
来做到这一点?
在适配器的 getItemViewType
上检查适配器是否有 0 个元素,如果有,return 一个不同的 viewType。
然后在您的 onCreateViewHolder
上检查 viewType 是否是您之前 return 编辑的类型并展开不同的视图。在这种情况下,带有该 TextView
的布局文件
编辑
如果这仍然不起作用,那么您可能想像这样以编程方式设置视图的大小:
Point size = new Point();
((WindowManager)itemView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(size);
然后当你膨胀你的视图调用时:
inflatedView.getLayoutParams().height = size.y;
inflatedView.getLayoutParams().width = size.x;
在定义 RecyclerView
的相同布局上,添加 TextView
:
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
android:text="@string/no_data_available" />
在 onCreate
或适当的回调中,您检查为 RecyclerView
提供数据的数据集是否为空。
如果数据集为空,则 RecyclerView
也为空。在这种情况下,消息会出现在屏幕上。
如果不是,请更改其可见性:
private RecyclerView recyclerView;
private TextView emptyView;
// ...
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
emptyView = (TextView) rootView.findViewById(R.id.empty_view);
// ...
if (dataset.isEmpty()) {
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
else {
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
对于我的项目,我制定了这个解决方案(RecyclerView
使用 setEmptyView
方法):
public class RecyclerViewEmptySupport extends RecyclerView {
private View emptyView;
private AdapterDataObserver emptyObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
Adapter<?> adapter = getAdapter();
if(adapter != null && emptyView != null) {
if(adapter.getItemCount() == 0) {
emptyView.setVisibility(View.VISIBLE);
RecyclerViewEmptySupport.this.setVisibility(View.GONE);
}
else {
emptyView.setVisibility(View.GONE);
RecyclerViewEmptySupport.this.setVisibility(View.VISIBLE);
}
}
}
};
public RecyclerViewEmptySupport(Context context) {
super(context);
}
public RecyclerViewEmptySupport(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RecyclerViewEmptySupport(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
if(adapter != null) {
adapter.registerAdapterDataObserver(emptyObserver);
}
emptyObserver.onChanged();
}
public void setEmptyView(View emptyView) {
this.emptyView = emptyView;
}
}
你应该使用它而不是 RecyclerView
class:
<com.maff.utils.RecyclerViewEmptySupport android:id="@+id/list1"
android:layout_height="match_parent"
android:layout_width="match_parent"
/>
<TextView android:id="@+id/list_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Empty"
/>
和
RecyclerViewEmptySupport list =
(RecyclerViewEmptySupport)rootView.findViewById(R.id.list1);
list.setLayoutManager(new LinearLayoutManager(context));
list.setEmptyView(rootView.findViewById(R.id.list_empty));
我使用 ViewSwitcher
<ViewSwitcher
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/switcher"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<TextView android:id="@+id/text_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/list_empty"
android:gravity="center"
/>
</ViewSwitcher>
在代码中,您将检查 cursor/dataset 并切换视图。
void showItems(Cursor items) {
if (items.size() > 0) {
mAdapter.switchCursor(items);
if (R.id.list == mListSwitcher.getNextView().getId()) {
mListSwitcher.showNext();
}
} else if (R.id.text_empty == mListSwitcher.getNextView().getId()) {
mListSwitcher.showNext();
}
}
如果您愿意,您还可以通过几行代码设置动画
mListSwitcher.setInAnimation(slide_in_left);
mListSwitcher.setOutAnimation(slide_out_right);
我在 RelativeLayout
中添加了 RecyclerView
和备选 ImageView
:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/no_active_jobs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_active_jobs" />
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
然后在 Adapter
:
@Override
public int getItemCount() {
if (mOrders.size() == 0) {
mRecyclerView.setVisibility(View.INVISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
}
return mOrders.size();
}
这是一个解决方案,只使用具有不同视图类型的自定义适配器来处理空情况。
public class EventAdapter extends
RecyclerView.Adapter<EventAdapter.ViewHolder> {
private static final int VIEW_TYPE_EVENT = 0;
private static final int VIEW_TYPE_DATE = 1;
private static final int VIEW_TYPE_EMPTY = 2;
private ArrayList items;
public EventAdapter(ArrayList items) {
this.items = items;
}
@Override
public int getItemCount() {
if(items.size() == 0){
return 1;
}else {
return items.size();
}
}
@Override
public int getItemViewType(int position) {
if (items.size() == 0) {
return VIEW_TYPE_EMPTY;
}else{
Object item = items.get(position);
if (item instanceof Event) {
return VIEW_TYPE_EVENT;
} else {
return VIEW_TYPE_DATE;
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
ViewHolder vh;
if (viewType == VIEW_TYPE_EVENT) {
v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_event, parent, false);
vh = new ViewHolderEvent(v);
} else if (viewType == VIEW_TYPE_DATE) {
v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_event_date, parent, false);
vh = new ViewHolderDate(v);
} else {
v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_event_empty, parent, false);
vh = new ViewHolder(v);
}
return vh;
}
@Override
public void onBindViewHolder(EventAdapter.ViewHolder viewHolder,
final int position) {
int viewType = getItemViewType(position);
if (viewType == VIEW_TYPE_EVENT) {
//...
} else if (viewType == VIEW_TYPE_DATE) {
//...
} else if (viewType == VIEW_TYPE_EMPTY) {
//...
}
}
public static class ViewHolder extends ParentViewHolder {
public ViewHolder(View v) {
super(v);
}
}
public static class ViewHolderDate extends ViewHolder {
public ViewHolderDate(View v) {
super(v);
}
}
public static class ViewHolderEvent extends ViewHolder {
public ViewHolderEvent(View v) {
super(v);
}
}
}
RVEmptyObserver
不使用自定义 RecyclerView
,扩展 AdapterDataObserver
是一种更简单的解决方案,允许设置自定义 View
,当列表中没有项目时显示:
用法示例:
RVEmptyObserver observer = new RVEmptyObserver(recyclerView, emptyView)
rvAdapter.registerAdapterDataObserver(observer);
Class:
public class RVEmptyObserver extends RecyclerView.AdapterDataObserver {
private View emptyView;
private RecyclerView recyclerView;
public RVEmptyObserver(RecyclerView rv, View ev) {
this.recyclerView = rv;
this.emptyView = ev;
checkIfEmpty();
}
private void checkIfEmpty() {
if (emptyView != null && recyclerView.getAdapter() != null) {
boolean emptyViewVisible = recyclerView.getAdapter().getItemCount() == 0;
emptyView.setVisibility(emptyViewVisible ? View.VISIBLE : View.GONE);
recyclerView.setVisibility(emptyViewVisible ? View.GONE : View.VISIBLE);
}
}
public void onChanged() { checkIfEmpty(); }
public void onItemRangeInserted(int positionStart, int itemCount) { checkIfEmpty(); }
public void onItemRangeRemoved(int positionStart, int itemCount) { checkIfEmpty(); }
}
这是我的 class 用于显示空视图、重试视图(当加载 api 失败时)和 RecyclerView
的加载进度
public class RecyclerViewEmptyRetryGroup extends RelativeLayout {
private RecyclerView mRecyclerView;
private LinearLayout mEmptyView;
private LinearLayout mRetryView;
private ProgressBar mProgressBar;
private OnRetryClick mOnRetryClick;
public RecyclerViewEmptyRetryGroup(Context context) {
this(context, null);
}
public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
if (child.getId() == R.id.recyclerView) {
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
return;
}
if (child.getId() == R.id.layout_empty) {
mEmptyView = (LinearLayout) findViewById(R.id.layout_empty);
return;
}
if (child.getId() == R.id.layout_retry) {
mRetryView = (LinearLayout) findViewById(R.id.layout_retry);
mRetryView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mRetryView.setVisibility(View.GONE);
mOnRetryClick.onRetry();
}
});
return;
}
if (child.getId() == R.id.progress_bar) {
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
}
}
public void loading() {
mRetryView.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
}
public void empty() {
mEmptyView.setVisibility(View.VISIBLE);
mRetryView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
}
public void retry() {
mRetryView.setVisibility(View.VISIBLE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
}
public void success() {
mRetryView.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
}
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
public void setOnRetryClick(OnRetryClick onRetryClick) {
mOnRetryClick = onRetryClick;
}
public interface OnRetryClick {
void onRetry();
}
}
activity_xml
<...RecyclerViewEmptyRetryGroup
android:id="@+id/recyclerViewEmptyRetryGroup">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"/>
<LinearLayout
android:id="@+id/layout_empty">
...
</LinearLayout>
<LinearLayout
android:id="@+id/layout_retry">
...
</LinearLayout>
<ProgressBar
android:id="@+id/progress_bar"/>
</...RecyclerViewEmptyRetryGroup>
来源在这里https://github.com/PhanVanLinh/AndroidRecyclerViewWithLoadingEmptyAndRetry
万一您使用的是 FirebaseRecyclerAdapter,这个 post 很有魅力
因为凯文的回答不完整。
如果您使用 RecyclerAdapter
的 notifyItemInserted
和 notifyItemRemoved
来更新数据集,这是更正确的答案。
请参阅下面另一个用户添加的 Kotlin 版本。
Java:
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkEmpty();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkEmpty();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkEmpty();
}
void checkEmpty() {
mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
});
科特林
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
checkEmpty()
}
fun checkEmpty() {
empty_view.visibility = (if (adapter.itemCount == 0) View.VISIBLE else View.GONE)
}
})
在自定义 RecyclerView 中使用 AdapterDataObserver
科特林:
RecyclerViewEnum.kt
enum class RecyclerViewEnum {
LOADING,
NORMAL,
EMPTY_STATE
}
RecyclerViewEmptyLoadingSupport.kt
class RecyclerViewEmptyLoadingSupport : RecyclerView {
var stateView: RecyclerViewEnum? = RecyclerViewEnum.LOADING
set(value) {
field = value
setState()
}
var emptyStateView: View? = null
var loadingStateView: View? = null
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}
private val dataObserver = object : AdapterDataObserver() {
override fun onChanged() {
onChangeState()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
onChangeState()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
onChangeState()
}
}
override fun setAdapter(adapter: RecyclerView.Adapter<*>?) {
super.setAdapter(adapter)
adapter?.registerAdapterDataObserver(dataObserver)
dataObserver.onChanged()
}
fun onChangeState() {
if (adapter?.itemCount == 0) {
emptyStateView?.visibility = View.VISIBLE
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
} else {
emptyStateView?.visibility = View.GONE
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
}
}
private fun setState() {
when (this.stateView) {
RecyclerViewEnum.LOADING -> {
loadingStateView?.visibility = View.VISIBLE
this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
emptyStateView?.visibility = View.GONE
}
RecyclerViewEnum.NORMAL -> {
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
emptyStateView?.visibility = View.GONE
}
RecyclerViewEnum.EMPTY_STATE -> {
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
emptyStateView?.visibility = View.VISIBLE
}
}
}
}
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/emptyLabelTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="empty" />
</LinearLayout>
<LinearLayout
android:id="@+id/loadingView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:indeterminate="true"
android:theme="@style/progressBarBlue" />
</LinearLayout>
<com.peeyade.components.recyclerView.RecyclerViewEmptyLoadingSupport
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
在activity中这样使用:
recyclerView?.apply {
layoutManager = GridLayoutManager(context, 2)
emptyStateView = emptyView
loadingStateView = loadingView
adapter = adapterGrid
}
// you can set LoadingView or emptyView manual
recyclerView.stateView = RecyclerViewEnum.EMPTY_STATE
recyclerView.stateView = RecyclerViewEnum.LOADING
另一种方法是使用 addOnChildAttachStateChangeListener
处理 RecyclerView
中的 appearing/disappearing 个子视图。
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
forEmptyTextView.setVisibility(View.INVISIBLE);
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) {
forEmptyTextView.setVisibility(View.VISIBLE);
}
});
如果你想在回收器视图为空时显示文本视图,你可以这样做:
ArrayList<SomeDataModel> arrayList = new ArrayList<>();
RecycleAdapter recycleAdapter = new RecycleAdapter(getContext(),project_Ideas);
recyclerView..setAdapter(recycleAdapter);
if(arrayList.isEmpty())
{
emptyTextView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
我假设你有 TextView
和XML喜欢这样
android:visibility="gone"
最简单的解决方案是使用 RecyclerView.AdapterDataObserver 并在您的适配器初始化后在您的 recyclerview 中注册它。
val emptyDataObserver = EmptyDataObserver(recycler_view, empty_data_parent)
yourAdapter.registerAdapterDataObserver(emptyDataObserver)
其中,recycler_view 和 empty_data_parent 是您 activity 中的布局,根据需要限制这些视图并使其可见性 GONE。然后使用图像和文本创建您自己的空数据集视图。
<include
android:id="@+id/empty_data_parent"
layout="@layout/item_empty_dataset"
android:layout_width="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
这里有一个例子 empty_data_set_view.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
tools:ignore="RtlHardcoded">
<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.422"
android:src="@drawable/ic_empty_dataset_1" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Title is called when title is placed"
android:padding="10dp"
android:fontFamily="@font/normal"
android:textStyle="bold"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView2" />
<TextView
android:id="@+id/sub_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=" Subtitle is called when title is placed. Subtitle is called when title is placed"
android:padding="5dp"
android:fontFamily="@font/normal"
android:gravity="center"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>
而这个 EmptyDataObserver class 将完成工作。
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class EmptyDataObserver constructor(rv: RecyclerView?, ev: View?): RecyclerView.AdapterDataObserver() {
private var emptyView: View? = null
private var recyclerView: RecyclerView? = null
init {
recyclerView = rv
emptyView = ev
checkIfEmpty()
}
private fun checkIfEmpty() {
if (emptyView != null && recyclerView!!.adapter != null) {
val emptyViewVisible = recyclerView!!.adapter!!.itemCount == 0
emptyView!!.visibility = if (emptyViewVisible) View.VISIBLE else View.GONE
recyclerView!!.visibility = if (emptyViewVisible) View.GONE else View.VISIBLE
}
}
override fun onChanged() {
super.onChanged()
checkIfEmpty()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
}
}
这如何显示空视图,同时使用 LiveData
过滤和更新 RecyclerView
ViewModel.getInstance().getCustomers?.observe(viewLifecycleOwner, {customerList ->
//assign your adapter with your list then
listAdapter?.notifyDataSetChanged()
isListItemEmpty(customerList?.isEmpty())
}
创建适配器和片段之间通信的接口并在适配器中注册它
interface EmptyListener {
fun isListEmpty(isEmpty: Boolean)
}
然后在publishResults里面调用:
dataList?.isEmpty()?.let { mListener?.isListEmpty(it) }
最后在 Fragment 中实现您的接口:
override fun isListEmpty(isEmpty: Boolean) {
if (isEmpty) {
your_list?.visibility = View.GONE
empty_view?.visibility = View.VISIBLE
} else {
empty_view?.visibility = View.GONE
your_list?.visibility = View.VISIBLE
}
}
我习惯在layout文件里面放一个特殊的view作为described in the ListActivity
documentation在没有数据的时候显示。此视图的 ID 为 "android:id/empty"
.
<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_data" />
我想知道如何使用新的 RecyclerView
来做到这一点?
在适配器的 getItemViewType
上检查适配器是否有 0 个元素,如果有,return 一个不同的 viewType。
然后在您的 onCreateViewHolder
上检查 viewType 是否是您之前 return 编辑的类型并展开不同的视图。在这种情况下,带有该 TextView
编辑
如果这仍然不起作用,那么您可能想像这样以编程方式设置视图的大小:
Point size = new Point();
((WindowManager)itemView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(size);
然后当你膨胀你的视图调用时:
inflatedView.getLayoutParams().height = size.y;
inflatedView.getLayoutParams().width = size.x;
在定义 RecyclerView
的相同布局上,添加 TextView
:
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
android:text="@string/no_data_available" />
在 onCreate
或适当的回调中,您检查为 RecyclerView
提供数据的数据集是否为空。
如果数据集为空,则 RecyclerView
也为空。在这种情况下,消息会出现在屏幕上。
如果不是,请更改其可见性:
private RecyclerView recyclerView;
private TextView emptyView;
// ...
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
emptyView = (TextView) rootView.findViewById(R.id.empty_view);
// ...
if (dataset.isEmpty()) {
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
else {
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
对于我的项目,我制定了这个解决方案(RecyclerView
使用 setEmptyView
方法):
public class RecyclerViewEmptySupport extends RecyclerView {
private View emptyView;
private AdapterDataObserver emptyObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
Adapter<?> adapter = getAdapter();
if(adapter != null && emptyView != null) {
if(adapter.getItemCount() == 0) {
emptyView.setVisibility(View.VISIBLE);
RecyclerViewEmptySupport.this.setVisibility(View.GONE);
}
else {
emptyView.setVisibility(View.GONE);
RecyclerViewEmptySupport.this.setVisibility(View.VISIBLE);
}
}
}
};
public RecyclerViewEmptySupport(Context context) {
super(context);
}
public RecyclerViewEmptySupport(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RecyclerViewEmptySupport(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
if(adapter != null) {
adapter.registerAdapterDataObserver(emptyObserver);
}
emptyObserver.onChanged();
}
public void setEmptyView(View emptyView) {
this.emptyView = emptyView;
}
}
你应该使用它而不是 RecyclerView
class:
<com.maff.utils.RecyclerViewEmptySupport android:id="@+id/list1"
android:layout_height="match_parent"
android:layout_width="match_parent"
/>
<TextView android:id="@+id/list_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Empty"
/>
和
RecyclerViewEmptySupport list =
(RecyclerViewEmptySupport)rootView.findViewById(R.id.list1);
list.setLayoutManager(new LinearLayoutManager(context));
list.setEmptyView(rootView.findViewById(R.id.list_empty));
我使用 ViewSwitcher
<ViewSwitcher
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/switcher"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<TextView android:id="@+id/text_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/list_empty"
android:gravity="center"
/>
</ViewSwitcher>
在代码中,您将检查 cursor/dataset 并切换视图。
void showItems(Cursor items) {
if (items.size() > 0) {
mAdapter.switchCursor(items);
if (R.id.list == mListSwitcher.getNextView().getId()) {
mListSwitcher.showNext();
}
} else if (R.id.text_empty == mListSwitcher.getNextView().getId()) {
mListSwitcher.showNext();
}
}
如果您愿意,您还可以通过几行代码设置动画
mListSwitcher.setInAnimation(slide_in_left);
mListSwitcher.setOutAnimation(slide_out_right);
我在 RelativeLayout
中添加了 RecyclerView
和备选 ImageView
:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/no_active_jobs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_active_jobs" />
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
然后在 Adapter
:
@Override
public int getItemCount() {
if (mOrders.size() == 0) {
mRecyclerView.setVisibility(View.INVISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
}
return mOrders.size();
}
这是一个解决方案,只使用具有不同视图类型的自定义适配器来处理空情况。
public class EventAdapter extends
RecyclerView.Adapter<EventAdapter.ViewHolder> {
private static final int VIEW_TYPE_EVENT = 0;
private static final int VIEW_TYPE_DATE = 1;
private static final int VIEW_TYPE_EMPTY = 2;
private ArrayList items;
public EventAdapter(ArrayList items) {
this.items = items;
}
@Override
public int getItemCount() {
if(items.size() == 0){
return 1;
}else {
return items.size();
}
}
@Override
public int getItemViewType(int position) {
if (items.size() == 0) {
return VIEW_TYPE_EMPTY;
}else{
Object item = items.get(position);
if (item instanceof Event) {
return VIEW_TYPE_EVENT;
} else {
return VIEW_TYPE_DATE;
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
ViewHolder vh;
if (viewType == VIEW_TYPE_EVENT) {
v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_event, parent, false);
vh = new ViewHolderEvent(v);
} else if (viewType == VIEW_TYPE_DATE) {
v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_event_date, parent, false);
vh = new ViewHolderDate(v);
} else {
v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_event_empty, parent, false);
vh = new ViewHolder(v);
}
return vh;
}
@Override
public void onBindViewHolder(EventAdapter.ViewHolder viewHolder,
final int position) {
int viewType = getItemViewType(position);
if (viewType == VIEW_TYPE_EVENT) {
//...
} else if (viewType == VIEW_TYPE_DATE) {
//...
} else if (viewType == VIEW_TYPE_EMPTY) {
//...
}
}
public static class ViewHolder extends ParentViewHolder {
public ViewHolder(View v) {
super(v);
}
}
public static class ViewHolderDate extends ViewHolder {
public ViewHolderDate(View v) {
super(v);
}
}
public static class ViewHolderEvent extends ViewHolder {
public ViewHolderEvent(View v) {
super(v);
}
}
}
RVEmptyObserver
不使用自定义 RecyclerView
,扩展 AdapterDataObserver
是一种更简单的解决方案,允许设置自定义 View
,当列表中没有项目时显示:
用法示例:
RVEmptyObserver observer = new RVEmptyObserver(recyclerView, emptyView)
rvAdapter.registerAdapterDataObserver(observer);
Class:
public class RVEmptyObserver extends RecyclerView.AdapterDataObserver {
private View emptyView;
private RecyclerView recyclerView;
public RVEmptyObserver(RecyclerView rv, View ev) {
this.recyclerView = rv;
this.emptyView = ev;
checkIfEmpty();
}
private void checkIfEmpty() {
if (emptyView != null && recyclerView.getAdapter() != null) {
boolean emptyViewVisible = recyclerView.getAdapter().getItemCount() == 0;
emptyView.setVisibility(emptyViewVisible ? View.VISIBLE : View.GONE);
recyclerView.setVisibility(emptyViewVisible ? View.GONE : View.VISIBLE);
}
}
public void onChanged() { checkIfEmpty(); }
public void onItemRangeInserted(int positionStart, int itemCount) { checkIfEmpty(); }
public void onItemRangeRemoved(int positionStart, int itemCount) { checkIfEmpty(); }
}
这是我的 class 用于显示空视图、重试视图(当加载 api 失败时)和 RecyclerView
public class RecyclerViewEmptyRetryGroup extends RelativeLayout {
private RecyclerView mRecyclerView;
private LinearLayout mEmptyView;
private LinearLayout mRetryView;
private ProgressBar mProgressBar;
private OnRetryClick mOnRetryClick;
public RecyclerViewEmptyRetryGroup(Context context) {
this(context, null);
}
public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
if (child.getId() == R.id.recyclerView) {
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
return;
}
if (child.getId() == R.id.layout_empty) {
mEmptyView = (LinearLayout) findViewById(R.id.layout_empty);
return;
}
if (child.getId() == R.id.layout_retry) {
mRetryView = (LinearLayout) findViewById(R.id.layout_retry);
mRetryView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mRetryView.setVisibility(View.GONE);
mOnRetryClick.onRetry();
}
});
return;
}
if (child.getId() == R.id.progress_bar) {
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
}
}
public void loading() {
mRetryView.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
}
public void empty() {
mEmptyView.setVisibility(View.VISIBLE);
mRetryView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
}
public void retry() {
mRetryView.setVisibility(View.VISIBLE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
}
public void success() {
mRetryView.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
}
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
public void setOnRetryClick(OnRetryClick onRetryClick) {
mOnRetryClick = onRetryClick;
}
public interface OnRetryClick {
void onRetry();
}
}
activity_xml
<...RecyclerViewEmptyRetryGroup
android:id="@+id/recyclerViewEmptyRetryGroup">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"/>
<LinearLayout
android:id="@+id/layout_empty">
...
</LinearLayout>
<LinearLayout
android:id="@+id/layout_retry">
...
</LinearLayout>
<ProgressBar
android:id="@+id/progress_bar"/>
</...RecyclerViewEmptyRetryGroup>
来源在这里https://github.com/PhanVanLinh/AndroidRecyclerViewWithLoadingEmptyAndRetry
万一您使用的是 FirebaseRecyclerAdapter,这个 post 很有魅力
因为凯文的回答不完整。
如果您使用 RecyclerAdapter
的 notifyItemInserted
和 notifyItemRemoved
来更新数据集,这是更正确的答案。
请参阅下面另一个用户添加的 Kotlin 版本。
Java:
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkEmpty();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkEmpty();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkEmpty();
}
void checkEmpty() {
mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
});
科特林
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
checkEmpty()
}
fun checkEmpty() {
empty_view.visibility = (if (adapter.itemCount == 0) View.VISIBLE else View.GONE)
}
})
在自定义 RecyclerView 中使用 AdapterDataObserver
科特林:
RecyclerViewEnum.kt
enum class RecyclerViewEnum {
LOADING,
NORMAL,
EMPTY_STATE
}
RecyclerViewEmptyLoadingSupport.kt
class RecyclerViewEmptyLoadingSupport : RecyclerView {
var stateView: RecyclerViewEnum? = RecyclerViewEnum.LOADING
set(value) {
field = value
setState()
}
var emptyStateView: View? = null
var loadingStateView: View? = null
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}
private val dataObserver = object : AdapterDataObserver() {
override fun onChanged() {
onChangeState()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
onChangeState()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
onChangeState()
}
}
override fun setAdapter(adapter: RecyclerView.Adapter<*>?) {
super.setAdapter(adapter)
adapter?.registerAdapterDataObserver(dataObserver)
dataObserver.onChanged()
}
fun onChangeState() {
if (adapter?.itemCount == 0) {
emptyStateView?.visibility = View.VISIBLE
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
} else {
emptyStateView?.visibility = View.GONE
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
}
}
private fun setState() {
when (this.stateView) {
RecyclerViewEnum.LOADING -> {
loadingStateView?.visibility = View.VISIBLE
this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
emptyStateView?.visibility = View.GONE
}
RecyclerViewEnum.NORMAL -> {
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
emptyStateView?.visibility = View.GONE
}
RecyclerViewEnum.EMPTY_STATE -> {
loadingStateView?.visibility = View.GONE
this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
emptyStateView?.visibility = View.VISIBLE
}
}
}
}
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/emptyLabelTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="empty" />
</LinearLayout>
<LinearLayout
android:id="@+id/loadingView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:indeterminate="true"
android:theme="@style/progressBarBlue" />
</LinearLayout>
<com.peeyade.components.recyclerView.RecyclerViewEmptyLoadingSupport
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
在activity中这样使用:
recyclerView?.apply {
layoutManager = GridLayoutManager(context, 2)
emptyStateView = emptyView
loadingStateView = loadingView
adapter = adapterGrid
}
// you can set LoadingView or emptyView manual
recyclerView.stateView = RecyclerViewEnum.EMPTY_STATE
recyclerView.stateView = RecyclerViewEnum.LOADING
另一种方法是使用 addOnChildAttachStateChangeListener
处理 RecyclerView
中的 appearing/disappearing 个子视图。
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
forEmptyTextView.setVisibility(View.INVISIBLE);
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) {
forEmptyTextView.setVisibility(View.VISIBLE);
}
});
如果你想在回收器视图为空时显示文本视图,你可以这样做:
ArrayList<SomeDataModel> arrayList = new ArrayList<>();
RecycleAdapter recycleAdapter = new RecycleAdapter(getContext(),project_Ideas);
recyclerView..setAdapter(recycleAdapter);
if(arrayList.isEmpty())
{
emptyTextView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
我假设你有 TextView
和XML喜欢这样
android:visibility="gone"
最简单的解决方案是使用 RecyclerView.AdapterDataObserver 并在您的适配器初始化后在您的 recyclerview 中注册它。
val emptyDataObserver = EmptyDataObserver(recycler_view, empty_data_parent)
yourAdapter.registerAdapterDataObserver(emptyDataObserver)
其中,recycler_view 和 empty_data_parent 是您 activity 中的布局,根据需要限制这些视图并使其可见性 GONE。然后使用图像和文本创建您自己的空数据集视图。
<include
android:id="@+id/empty_data_parent"
layout="@layout/item_empty_dataset"
android:layout_width="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
这里有一个例子 empty_data_set_view.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
tools:ignore="RtlHardcoded">
<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.422"
android:src="@drawable/ic_empty_dataset_1" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Title is called when title is placed"
android:padding="10dp"
android:fontFamily="@font/normal"
android:textStyle="bold"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView2" />
<TextView
android:id="@+id/sub_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=" Subtitle is called when title is placed. Subtitle is called when title is placed"
android:padding="5dp"
android:fontFamily="@font/normal"
android:gravity="center"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>
而这个 EmptyDataObserver class 将完成工作。
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class EmptyDataObserver constructor(rv: RecyclerView?, ev: View?): RecyclerView.AdapterDataObserver() {
private var emptyView: View? = null
private var recyclerView: RecyclerView? = null
init {
recyclerView = rv
emptyView = ev
checkIfEmpty()
}
private fun checkIfEmpty() {
if (emptyView != null && recyclerView!!.adapter != null) {
val emptyViewVisible = recyclerView!!.adapter!!.itemCount == 0
emptyView!!.visibility = if (emptyViewVisible) View.VISIBLE else View.GONE
recyclerView!!.visibility = if (emptyViewVisible) View.GONE else View.VISIBLE
}
}
override fun onChanged() {
super.onChanged()
checkIfEmpty()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
}
}
这如何显示空视图,同时使用 LiveData
过滤和更新 RecyclerView ViewModel.getInstance().getCustomers?.observe(viewLifecycleOwner, {customerList ->
//assign your adapter with your list then
listAdapter?.notifyDataSetChanged()
isListItemEmpty(customerList?.isEmpty())
}
创建适配器和片段之间通信的接口并在适配器中注册它
interface EmptyListener {
fun isListEmpty(isEmpty: Boolean)
}
然后在publishResults里面调用:
dataList?.isEmpty()?.let { mListener?.isListEmpty(it) }
最后在 Fragment 中实现您的接口:
override fun isListEmpty(isEmpty: Boolean) {
if (isEmpty) {
your_list?.visibility = View.GONE
empty_view?.visibility = View.VISIBLE
} else {
empty_view?.visibility = View.GONE
your_list?.visibility = View.VISIBLE
}
}