Knowledge is a treasure but practice is the key to it.
在android的前端开发过程中,对于前端的加载效果,界面友好是很重要的。
需要在界面加载过程中的变化中友好的显示:
1:加载中
2:加载成功
3:加载失败
4:网络异常
5:请求数据接口暂时没有数据
etc,所以,需要我们自己书写相关的界面加载类,来做一个统一的整理
在刚开始遇到这样的问题时,就个人而言,用的最笨的办法也是最耗性能。在原有的正常UI界面中添加 include 标签,然后根据逻辑进行隐藏显示。很不自在。之后在优化中总结经验,创建自己的界面类。
正题:
step1:自定义View extends FrameLayout
step2:创建enum来确定相应的状态
step3:创建相应界面的layout
package com.welive.defineview;
import ···
/**
* author:wedfrend
* email:wedfrend@yeah.net
* create:2017/8/29 11:11
* desc: 在手机端加载过程中,会出现很多的状况
*
* 1:加载中
* 2:数据出错
* 3:网络异常
* 4:重新加载
*
* 为了方便的统一管理,所以自定义View来进行相应的加载状态方法
*
*/
public class NavFrameLayout extends FrameLayout {
private FrameLayout wholeFrameLayout;
//布局中的子页面
private View mContentView;
private LayoutInflater layoutInflater;
//不同的状态
private View statusView;
private EnumContent.statusPage statusPage = EnumContent.statusPage.LOADING;
public EnumContent.statusPage getStatusPage() {
return statusPage;
}
public void setStatusPage(EnumContent.statusPage statusPage) {
this.statusPage = statusPage;
}
/**
* 默认情况下的三种布局界面
*/
private final int loadingLayoutId = R.layout.z_loading_layout;//加载中
private final int dateErrorLayoutId = R.layout.z_daterror_layout;//数据异常
private final int netErrorLayoutId = R.layout.z_neterror_layout;//网络异常
public NavFrameLayout(Context context) {
super(context,null);
}
public NavFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public NavFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
InitDefault(context, attrs, defStyleAttr);
}
//初始化
public void InitDefault(Context context, AttributeSet attrs, int defStyleAttr){
wholeFrameLayout = new FrameLayout(context);
layoutInflater = LayoutInflater.from(context);
wholeFrameLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
//该方法可以在父类中查看,是直接将该布局放置在子布局的第一个位置
addView(wholeFrameLayout);
//加载界面的显示
setViewLoading(loadingLayoutId);
}
/**
进行界面的判断,在layout下该自定义VIEW下必须有且只有一个子布局Ui,但是实际上我们在代码中添加一个View,layout中一个,所以判断必须为2
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(getChildCount() != 2){
//抛出运行时异常
throw new RuntimeException(NavFrameLayout.class.getSimpleName() + "必须有且只有一个子控件");
}
mContentView = getChildAt(1);
//完成之后将该View先进行隐藏
mContentView.setVisibility(View.GONE);
}
/**
* 正在加载
* @param layoutResID
*/
protected SwipeRefreshLayout swipeRefreshLayout;
public void setViewLoading(@LayoutRes int layoutResID){
//赋值状态
statusPage = EnumContent.statusPage.LOADING;
//判断是否statusView是否已经存在,存在的话先做一次清除,因为没有必要一直存在
if (statusView != null && statusView.getParent() != null) {
ViewGroup parent = (ViewGroup) statusView.getParent();
parent.removeView(statusView);
}
//接下来做自己界面流程
statusView = layoutInflater.inflate(layoutResID,null);
if (statusView != null) {
statusView.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
wholeFrameLayout.addView(statusView);
}
statusView.setVisibility(View.VISIBLE);
swipeRefreshLayout = ((SwipeRefreshLayout) statusView.findViewById(R.id.refresh_loading));
swipeRefreshLayout.setProgressViewOffset(true, 50, 100);
//设置下拉圆圈的大小,两个值 LARGE, DEFAULT
swipeRefreshLayout.setSize(SwipeRefreshLayout.DEFAULT);
//下拉刷新调用的一个接口
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary, R.color.colorPrimaryDark, R.color.colorPrimaryDarkContentActivity);
//这里直接调用swipeRefrehLayout的加载状态
swipeRefreshLayout.post(() -> swipeRefreshLayout.setRefreshing(true));
}
/**
* 设置界面正常加载的情况下,对于不同状态需要做的处理如下,在自己主逻辑程序中只需调用方法,相应的处理状态交给该方法执行
*/
public void setNormal(){
if(statusView.getVisibility() == View.VISIBLE){
//异常界面隐藏
statusView.setVisibility(View.GONE);
//正常界面显示
mContentView.setVisibility(View.VISIBLE);
}
//处理不同的情况
if(statusPage.equals(EnumContent.statusPage.NONE)){
}
//正在加载中
if(statusPage.equals(EnumContent.statusPage.LOADING)){
//加载界面显示
swipeRefreshLayout.post(() -> swipeRefreshLayout.setRefreshing(false));
return;
}
//数据出现异常
if(statusPage.equals(EnumContent.statusPage.DATEERROR)){
return;
}
//网络出现异常
if(statusPage.equals(EnumContent.statusPage.NETERROR)){
return;
}
setStatusPage(EnumContent.statusPage.NONE);
}
/**
*
* @param status 异常状态
* @param imgId 图片ID
* @param StringId 提示字段ID
*/
public void setException(EnumContent.statusPage status,int imgId,int StringId){
if(statusPage.equals(EnumContent.statusPage.NONE)){
T.show(R.string.NetError,0);
}
statusPage = status;
if(statusView.getVisibility() == View.GONE){
//正常界面隐藏
mContentView.setVisibility(View.GONE);
//异常界面显示
statusView.setVisibility(View.VISIBLE);
}
//处理不同的情况
if(status.equals(EnumContent.statusPage.NONE)){
}
//正在加载中
if(status.equals(EnumContent.statusPage.LOADING)){
//加载界面显示
setViewLoading(loadingLayoutId);
return;
}
//数据出现异常
if(status.equals(EnumContent.statusPage.DATEERROR)){
setViewDateError(dateErrorLayoutId);
return;
}
//网络出现异常
if(status.equals(EnumContent.statusPage.NETERROR)){
setViewNetError(netErrorLayoutId);
return;
}
}
/**
* 数据异常
* @param layoutResID
*/
public void setViewDateError(@LayoutRes int layoutResID){
if (statusView != null && statusView.getParent() != null) {
ViewGroup parent = (ViewGroup) statusView.getParent();
parent.removeView(statusView);
}
statusView = layoutInflater.inflate(layoutResID,null);
if (statusView != null) {
statusView.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
wholeFrameLayout.addView(statusView);
}
//下面要做些事情
}
/**
* 网络异常
* @param layoutResID
*/
public void setViewNetError(@LayoutRes int layoutResID){
if (statusView != null && statusView.getParent() != null) {
ViewGroup parent = (ViewGroup) statusView.getParent();
parent.removeView(statusView);
}
statusView = layoutInflater.inflate(layoutResID,null);
if (statusView != null) {
statusView.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
wholeFrameLayout.addView(statusView);
}
statusView.findViewById(R.id.tv_reloading).setOnClickListener(v ->
{
setException(EnumContent.statusPage.LOADING,0,0);
loadingListener.onLoading();
});
}
//这里还需要一个接口的触发时间,来证实界面的可行性
public interface LoadingListener{
void onLoading();
}
private LoadingListener loadingListener;
public LoadingListener getLoadingListener() {
return loadingListener;
}
public void setLoadingListener(LoadingListener loadingListener) {
this.loadingListener = loadingListener;
}
}
知识要点:
1:该类中需要注意的就是一个onFinishInflate()方法的判断以及我们在原有布局中添加一个布局。
2:addView()方法调用父类的方法
/**
* <p>Adds a child view. If no layout parameters are already set on the child, the
* default parameters for this ViewGroup are set on the child.</p>
*
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
*
* @see #generateDefaultLayoutParams()
*/
public void addView(View child) {
addView(child, -1);
}
所以此时getChildAt(int i)中,角标0表示我们添加的View,而角标为1的表示在layout中的View
3:对于各种状态的布局以及状态发生变化的时候先清空之前的内容,在加载新的界面。
由于不同公司的定制,如果将该类作为aar文件来进行引用反而会带来更加复杂的封装,所以还是由每个开发自己去写一套适用于自己公司的类更为合适。
最终在界面绘制完成,跟布局中有两个子布局
相关实践代码请在Github中进行下载:
https://github.com/wedfrendwang/NavLayout
Everyone gets tired.No one can take the pain for you. You have to go through it and grow up.
心烦事比较多,激励一下自己。希望从事编程行业的同仁们加油。