很多童鞋们认为ListView的setEmptyView设置空界面无效的。通常在网上查了查,然后来按照网谁上的做法直接复制粘贴一下。效果出来就OK了。身为一个开发者,我们既要知其然,也要知道其所以然。
目前我们大部分是这样做的:
View view;
inflater = LayoutInflater.from(this);
view =inflater.inflate(R.layout.empty_nodata,null);
((ViewGroup)llr_recycleView_queryist.getParent()).addView(emptyView);
lr_recycleView_query.setEmptyView(emptyView);
setEmptyView()其实是AdapterView的方法,而我们开发中常用到的ListView, GridView, ExpandableListView等都是继承于AdapterView的,所以可以直接调用这个方法。
so问题来了。
为什么一定要加上
((ViewGroup)llr_recycleView_queryist.getParent()).addView(emptyView);
这行代码呢?
请看源码:
/**
* Sets the view to show if the adapter is empty
*/
@android.view.RemotableViewMethod
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
// If not explicitly specified this view is important for accessibility.
if (emptyView != null
&& emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
}
从上面可以看出当emptyView部位空时,先通过updateEmptyStatus(empty);进行更新当前的View。
在setEmptyView方法中将传过来的emptyView赋值给全局的mEmptyView。在其他地方也可以对其进行处理。
接下来是看一下updateEmptyStatus(empty)的源码:
/**
* Update the status of the list based on the empty parameter. If empty is true and
* we have an empty view, display it. In all the other cases, make sure that the listview
* is VISIBLE and that the empty view is GONE (if it's not null).
*/
private void updateEmptyStatus(boolean empty) {
if (isInFilterMode()) {
empty = false;
}
if (empty) {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}
// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
if (mDataChanged) {
this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
}
}
updateEmptyStatus源码很简单,根据empty这个状态值进行设定mEmptyView 是否是显示。
如果adapter部位空,则执行:
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
为空时的时候:
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}
源码中仅仅只是对mEmptyView进行了隐藏和显示。并未对其进行说明。这就表明了,
mEmptyView和当前的list或者是GridView及其其他View是同一级的关系。同时也说明了为什么需要用到((ViewGroup)llr_recycleView_queryist.getParent()).addView(emptyView); 才能起到效果。
这也可能是Android源码时的一个不足之处。针对于Android上的有些老早的老司机早就想好了对策。比如说PullToRefreshAdapterViewBase就重写了setemptyView方法。
public final void setEmptyView(View newEmptyView) {
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
if (null != newEmptyView) {
// New view needs to be clickable so that Android recognizes it as a
// target for Touch Events
newEmptyView.setClickable(true);
ViewParent newEmptyViewParent = newEmptyView.getParent();
if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
}
// We need to convert any LayoutParams so that it works in our
// FrameLayout
FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
if (null != lp) {
refreshableViewWrapper.addView(newEmptyView, lp);
} else {
refreshableViewWrapper.addView(newEmptyView);
}
}
if (mRefreshableView instanceof EmptyViewMethodAccessor) {
((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
} else {
mRefreshableView.setEmptyView(newEmptyView);
}
mEmptyView = newEmptyView;
}
这既是为什么有些时候不做处理也可以有效果。
对EmptyView的封装
**
* 功能: 列表空界面显示
* 用于listView ,GridView ,RecycleView 的setEmpety方法
* @author yuyahao
* 备注: 其他童鞋们可对其进行拓展 eg: 添加监听,切换布局
*/
public class EmptyView extends LinearLayout {
private TextView tv_no_message;
private ImageView iv_empety_mageger;
private LinearLayout ll_no_manager;
private LinearLayout ll_no_message_root;
public EmptyView(Context context) {
super(context);
init();
// TODO Auto-generated constructor stub
}
public EmptyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// TODO Auto-generated method stub
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(R.layout.empty_view, null);
iv_empety_mageger = (ImageView) view.findViewById(R.id.iv_empety_mageger);
tv_no_message = (TextView) view.findViewById(R.id.tv_no_message);
ll_no_manager = (LinearLayout) view.findViewById(R.id.ll_no_manager);
ll_no_message_root = (LinearLayout) view.findViewById(R.id.ll_no_message_root);
addView(view);
}
/**
* 一定要调用该方法
* @param
*/
public View mustCallInitWay(View view){
if(view != null){
ViewGroup.LayoutParams params = ll_no_message_root.getLayoutParams();
params.width = DensityUtil.getScreenIntWidth(MyApplication.getContext());
params.height = DensityUtil.getScreenIntHeight(MyApplication.getContext()) - DensityUtil.sp2px(MyApplication.getContext(),50);
((ViewGroup)view.getParent()).addView(this,params);
}
return this;
}
public void setNoMessageText(CharSequence text) {
tv_no_message.setText(text);
tv_no_message.setVisibility(View.VISIBLE);
}
/********修改文字的颜色**********/
public void setMessageTextColor2(int colorResId) {
tv_no_message.setTextColor(colorResId);
tv_no_message.setVisibility(View.VISIBLE);
}
/**
* 显示不同的文字提示及其图片提示
*/
public View setMyManager(String showNoTip,int showNoIamgeViewResId,int textSize,int colorResId){
iv_empety_mageger.setImageResource(showNoIamgeViewResId);
tv_no_message.setTextColor(colorResId);
tv_no_message.setText(showNoTip);
tv_no_message.setTextSize(textSize);
tv_no_message.setVisibility(View.VISIBLE);
return this;
}
/**
* 显示不同的文字提示及其图片提示
*/
public View setMyManager(String showNoTip,int showNoIamgeViewResId){
iv_empety_mageger.setImageResource(showNoIamgeViewResId);
tv_no_message.setText(showNoTip);
tv_no_message.setVisibility(View.VISIBLE);
return this;
}
/**
* 显示不同的文字提示及其图片提示
*/
public View setMyManager(int showNoIamgeViewResId){
iv_empety_mageger.setImageResource(showNoIamgeViewResId);
iv_empety_mageger.setVisibility(View.VISIBLE);
return this;
}
/**
* 显示不同的文字提示
*/
public View setMyManager(String showNoTip){
tv_no_message.setText(showNoTip);
tv_no_message.setVisibility(View.VISIBLE);
return this;
}
/**
* 是否显示文字
*/
public View isShowTextTipMassager(boolean isShow){
if(isShow){
tv_no_message.setVisibility(View.VISIBLE);
}
return this;
}
/**
* 是否显示文字
*/
public View isShowTextTipMassager(boolean isShow,String showNoTip){
if(isShow){
tv_no_message.setVisibility(View.VISIBLE);
tv_no_message.setText(showNoTip);
}else{
tv_no_message.setVisibility(View.GONE);
tv_no_message.setText(showNoTip);
}
return this;
}
/**
* 是否显示图片
*/
public View isShowIamgeMassager(boolean isShow,int showNoIamgeViewResId){
if(isShow){
iv_empety_mageger.setVisibility(View.VISIBLE);
iv_empety_mageger.setImageResource(showNoIamgeViewResId);
}else{
iv_empety_mageger.setVisibility(View.GONE);
}
return this;
}
/**
* 是否显示图片
*/
public View isShowIamgeMassager(boolean isShow){
if(isShow){
iv_empety_mageger.setVisibility(View.VISIBLE);
}else{
iv_empety_mageger.setVisibility(View.GONE);
}
return this;
}
}
如何调用:
EmptyView emptyView = new EmptyView(this);
emptyView.mustCallInitWay(lr_recycleView_query);
lr_recycleView_query.setEmptyView(emptyView);
注意:如果你的子布局中有剁成父布局进行嵌套,这个时候
((ViewGroup)view.getParent()).addView(this);
这个方法是无效的。addView的时候布局中的大小match_parent是没有效果的,它总是以包裹内容进行填充的。
显示效果:
这个时候一定要调用
((ViewGroup)view.getParent()).addView(this,params);
对整体布局用代码进行设置大小才起到效果。
调用addView(this,params)之后正确显示:
这里的params是最外层的布局大小:
ViewGroup.LayoutParams params = ll_no_message_root.getLayoutParams();
params.width = DensityUtil.getScreenIntWidth(MyApplication.getContext());
params.height = DensityUtil.getScreenIntHeight(MyApplication.getContext())
- DensityUtil.sp2px(MyApplication.getContext(),50);
((ViewGroup)view.getParent()).addView(this,params);
该工具类封装好之后可用于ListView,GridView,RecycleView,PullToRefreshView及其自定义相关的组件的应用。直接调用其方法setEmptyView()即可。
曾经踩过的坑:
addView调用之后导致子布局的android:layout_width=”match_parent”属性或者 android:layout_width=”fll_parent”
((ViewGroup)view.getParent()).addView(params);
属性无效。
我们先看源码addView(View view):
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
从源码中可以知道,如果不传入 LayoutParams.params的话,就会有一个默认的params。
那么再看看源码:
/**
* Returns a set of default layout parameters. These parameters are requested
* when the View passed to {@link #addView(View)} has no layout parameters
* already set. If null is returned, an exception is thrown from addView.
*
* @return a set of default layout parameters or null
*/
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
到这里我们就可以清楚的看到:
如果一个View没有set LayoutParams,在该View被添加到一个ViewGroup里时,ViewGroup会为该View创建一个默认的LayoutParams。所以如果题中的view已经存在于一个ViewGroup中,view.getLayoutParams()便会得到ViewGroup为其创建的默认LayoutParams。而这个默认LayoutParams会因ViewGroup而变,而这里的ViewGroup是指得当前的LinearLayout。LayoutParams是LayoutParams.WRAP_CONTENT将会是子布局的包裹内容。
因此如果只调用addView(View v)不设置params参数将会无效。
SwipeRefreshLayout和ListView的EmptyView共存冲突的问题
最好的方式是: 将ListView和EmptyView分离,让他们两个分别被两个SwipeRefreshLayout包裹
参考链接:http://gundumw100.iteye.com/blog/2177595
相信自己,没有做不到的,只有想不到的
如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809
微信公众号:终端研发部
(欢迎关注学习和交流)