Android框架思考–界面View封装

作者 : 开心源码 本文共14362个字,预计阅读时间需要36分钟 发布时间: 2022-05-12 共190人阅读

前言

Android项目不论使用什么框架结构,承载界面的必然少不了Activity或者者Fragment,而对于一个客户界面来说,有少量业务逻辑的解决是通用的,比方请求网络时需要有loading框,比方网络错误时需要界面有对应提醒,比方通用的导航栏,比方每个界面都用activity就需要在Manifest.xml文件中配置等等,这些是否做少量封装,可以让开发者只关注具体界面的具体逻辑,快速的实现一个界面?

思考方向

基于以上的问题,我们要封装的内容需要满足以下需求:

  • 不用每一个客户界面都在Manifest.xml文件中配置
  • 通用的导航栏解决
  • 通过简单的继承即可以自动实现loading(这个后续还会和网络请求关联)、异常界面(没数据、无网络等)显示等

客户界面的承载选择

假如想不在Manifest.xml中配置很多用到的客户界面,那么使用Fragment就是我们的必然选择了,使用Fragment有两种方式,一种是所有的Fragment都有一个公用的Activity来承载,每一次客户切换界面其实还是切换Activity,第二个就是只启动一个Activity,在Activity中切换Fragment以达到界面的切换。第二种看起来更正当一点,但是对于界面生命周期的管理以及少量公用的参数用不好就会出现混乱的情况,所以我们采用第一种方案。

方案思路

先定义一个接口,封装基本的界面操作方法(loading、toast、显示错误信息等),而后用一个BaseFragment来实现该接口方法,在用一个activity来承载这个实现了BaseFragment的不同Fragment,传递的参数中告诉Activity需要加载的Fragment名字,通过这样,只要要注册一个承载Acitivty即可以实现显示不同的客户界面。如下图:

灵魂画手画的流程

具体实现

按照上面思路首先要定义好一个客户界面基本的方法

  • IView.java 在MVP模式中也会复用到
package com.kotlin.anonyper.testapplication.base;/** * 普通view的操作接口 * TestApplication * Created by anonyper on 2018/12/17. */public interface IView {    /**     * 弹出通知     *     * @param message     */    void showToast(String message);    /**     * 隐藏loading条     */    void hideLoading();    /**     * 控制显示loading     *     * @param message    loading内容     * @param cancelAble 能否可取消     */    void showLoading(String message, boolean cancelAble);    /**     * 显示内容部分view     */    void showContentView();   /**     * 显示异常部分view     *     * @param imageRes 显示的资源图片     * @param message 显示的信息     */    void showExcptionView(int imageRes, String message);}

而后用Fragment来实现IView接口,实现其中的方法

/** * 基本的fragment * TestApplication * Created by anonyper on 2018/12/18. */public abstract class BaseFragment extends Fragment implements IView {    private ProgressDialog progressDialog;    @Override    public void showToast(String message) {        Toast.makeText(this.getContext(), message, Toast.LENGTH_LONG).show();    }    @Override    public void hideLoading() {        if (progressDialog != null && progressDialog.isShowing()) {            progressDialog.dismiss();        }    }    @Override    public void showLoading(String message, boolean cancelAble) {        if (TextUtils.isEmpty(message)) {            message = "";        }        if (progressDialog == null) {            progressDialog = new ProgressDialog(this.getActivity());        }        if (this.getActivity().isFinishing()) {            return;        }        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {            if (this.getActivity().isDestroyed()) {                return;            }        }        progressDialog.setMessage(message);        progressDialog.setCanceledOnTouchOutside(true);        progressDialog.setCancelable(cancelAble);        if (!progressDialog.isShowing()) {            progressDialog.show();        }    }    @Override    public void showContentView() {    }    @Override    public void showExcptionView(int imageRes,String message) {    }}

上述中showContentViewshowExcptionView没有具体的实现,这个会放到BaseFragment的子类中来实现。
针对大多数客户界面(带有网络请求),有一个同样式的title导航、异常界面以及数据界面切换显示逻辑。针对这种情况,我们封装一个SimpleBaseFragmen,将显示内容和异常界面用FrameLayout容器并列存放,而后和导航栏的view通过LinearLayout容器竖直排列。先看公用的title类:

/** * 公共标题 */public class TitleBar extends RelativeLayout {    Context mContext;    View titleView;    @BindView(R.id.imgv_titleleft)    ImageView imgvTitleleft;    @BindView(R.id.rlt_titleleft)    RelativeLayout rltTitleLeft;    @BindView(R.id.tv_title)    TextView tvTitle;    @BindView(R.id.imgv_titleright)    ImageView imgvTitleright;    @BindView(R.id.tv_titleright)    TextView tvTitleright;    @BindView(R.id.rlt_titleright)    RelativeLayout rltTitleright;    @BindView(R.id.tv_titleline)    View tvTitleline;    @BindView(R.id.rlt_title)    RelativeLayout rltTitle;    public TitleBar(Context context) {        super(context);        this.mContext = context;        initView();    }    public TitleBar(Context context, AttributeSet attrs) {        super(context, attrs);        this.mContext = context;        initView();    }    public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.mContext = context;        initView();    }    private void initView() {        titleView = View.inflate(getContext(), R.layout.view_title_bar, this);        ButterKnife.bind(this, titleView);    }    public View getTitleView() {        return titleView;    }    public void setTitle(String title) {        if (tvTitle != null) {            tvTitle.setText(title);        }    }    public void setTitle(int res) {        if (res > 0 && mContext != null)            setTitle(mContext.getResources().getString(res));    }    public View getLeftView() {        return rltTitleLeft;    }    public View getRightView() {        return rltTitleright;    }    public void setLeftImage(int res) {        if (imgvTitleleft != null && res > 0) {            imgvTitleleft.setImageResource(res);        }    }    public void setRightImage(int res) {        if (imgvTitleright != null && res > 0) {            imgvTitleright.setImageResource(res);        }    }    public void setRightText(String rightText) {        if (tvTitleright != null) {            tvTitleright.setText(rightText);        }    }    public void setRightText(int rightText) {        if (tvTitleright != null && rightText > 0) {            tvTitleright.setText(rightText);        }    }    public void hideTitle() {        if (titleView != null) {            titleView.setVisibility(View.GONE);        }    }    public void showTitle() {        if (titleView != null) {            titleView.setVisibility(View.VISIBLE);        }    }    public void hideLeftView() {        if (rltTitleLeft != null) {            rltTitleLeft.setVisibility(View.INVISIBLE);        }    }    public void showLeftView() {        if (rltTitleLeft != null) {            rltTitleLeft.setVisibility(View.VISIBLE);        }    }    public void hideLeftImage() {        if (imgvTitleleft != null) {            imgvTitleleft.setVisibility(View.GONE);            rltTitleLeft.setVisibility(View.INVISIBLE);        }    }    public void showLeftImage() {        if (imgvTitleleft != null) {            imgvTitleleft.setVisibility(View.VISIBLE);            rltTitleLeft.setVisibility(View.VISIBLE);        }    }    public void showRightText() {        if (tvTitleright != null) {            tvTitleright.setVisibility(View.VISIBLE);            rltTitleright.setVisibility(View.VISIBLE);        }    }    public void hideRightText() {        if (tvTitleright != null) {            tvTitleright.setVisibility(View.GONE);        }    }    public void showRightImage() {        if (imgvTitleright != null) {            imgvTitleright.setVisibility(View.VISIBLE);            rltTitleright.setVisibility(View.VISIBLE);        }    }    public void hideRightImage() {        if (imgvTitleright != null) {            imgvTitleright.setVisibility(View.GONE);        }    }    public void hideRightView() {        if (rltTitleright != null) {            rltTitleright.setVisibility(View.INVISIBLE);        }    }    public void showRightView() {        if (rltTitleright != null) {            rltTitleright.setVisibility(View.VISIBLE);        }    }    public void hideTitleLine() {        if (tvTitleline != null) {            tvTitleline.setVisibility(View.GONE);        }    }    public void showTitleLine() {        if (tvTitleline != null) {            tvTitleline.setVisibility(View.VISIBLE);        }    }    public void setRightClickListener(View.OnClickListener listener) {        if (listener != null) {            getRightView().setOnClickListener(listener);        }    }}

写一个类SimpleBaseFragment继承BaseFragment类:有几个重点方法:

  • onAttach 用来回去上下文content
@Override    public void onAttach(Context context) {        super.onAttach(context);        this.context = context;    }
  • getExcptionView 获取异常的view,假如异常view不满足使用,可以重写该方法
public View getExcptionView() {    View errorView = View.inflate(this.getContext(), R.layout.view_error_empty, null);    errorView.setVisibility(View.GONE);    return errorView;}
  • initTitle 解决title,增加左上角返回
public void initTitle() {        if (mTitleBar != null) {            mTitleBar.showLeftView();            mTitleBar.getLeftView().setOnClickListener(v -> {                try {                    hideSoftInput();                } catch (Exception e) {                    e.printStackTrace();                }                getActivity().finish();            });        }    }
  • 控制内容view和异常的显示
@Override    public void showContentView() {        if (mainContentView != null) {            mainContentView.setVisibility(View.VISIBLE);        }        if (excptionView != null) {            excptionView.setVisibility(View.GONE);        }    }    @Override    public void showExcptionView(int imageRes, String message) {        if (mainContentView != null) {            mainContentView.setVisibility(View.GONE);        }        if (excptionView != null) {            excptionView.setVisibility(View.VISIBLE);            if (imageRes > 0) {                ImageView image = excptionView.findViewById(R.id.error_imageview);                if (image != null) {                    image.setImageResource(imageRes);                }            }            if (!TextUtils.isEmpty(message)) {                TextView descView = excptionView.findViewById(R.id.desc);                if (descView != null) {                    descView.setText(message);                }            }            excptionView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    onExcptionViewClick();                }            });        }    }
  • onCreateView方法的实现
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        super.onCreateView(inflater, container, savedInstanceState);        int viewId = getLayoutId();        if (viewId > 0) {            contentView = inflater.inflate(viewId, null, false);//            if (contentView != null)                unbinder = ButterKnife.bind(this, contentView);        }        mTitleBar = new TitleBar(context);//标题栏        mRootView = new LinearLayout(context);//根view  里面包含title和一个包含其余内容的view        mRootView.setBackgroundColor(getResources().getColor(R.color.app_bg_theme));        mRootView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        mRootView.setOrientation(LinearLayout.VERTICAL);        mRootView.addView(mTitleBar);        FrameLayout frameLayout = new FrameLayout(context);//将除了title之外的view放进该frameLayout        frameLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        mainContentView = new LinearLayout(context);        mainContentView.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        mainContentView.setOrientation(LinearLayout.VERTICAL);        if (contentView != null) {            contentView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));            mainContentView.addView(contentView);        }        frameLayout.addView(mainContentView);        excptionView = getExcptionView();        if (excptionView != null) {            frameLayout.addView(excptionView);        }        mRootView.addView(frameLayout);        //设置Toolbar相关        initTitle();        //初始化控件        initView(contentView, getArguments());        return mRootView;    }/**     * 获取要展现的资源view     *     * @return 展现view的layout资源     */    public abstract int getLayoutId();

通过以上方法,我们封装了一个基本的常用的Fragment,在具体使用的过程中,我们通过继承该SampleBaseFragment,实现public abstract int getLayoutId();方法,就会自动的增加title、异常view等逻辑。

具体用法:

MainFragment.java

/** * TestApplication * Created by anonyper on 2018/12/18. */public class MainFragment extends SimpleBaseFragment {    View parent_view;    TextView text;    Button button;    ImageView imageView;    SelectPictureManager selectPictureManager;    @Override    public int getLayoutId() {        return R.layout.activity_main;    }    @Override    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {        super.onViewCreated(view, savedInstanceState);        parent_view = view.findViewById(R.id.parent_view);        text = view.findViewById(R.id.text);        button = view.findViewById(R.id.button);        imageView = view.findViewById(R.id.imageView);        button.setOnClickListener(myview -> initSelectPictureManager());//这是lambda的使用方式    }    void initSelectPictureManager() {        selectPictureManager = new SelectPictureManager(this.getActivity());        selectPictureManager.setPictureSelectListner(new SelectPictureManager.PictureSelectListner() {            @Override            public void onPictureSelect(String imagePath) {                LogUtil.i("TAG", "选择图片的路径是:" + imagePath);                text.setText("选择图片的路径是:" + imagePath);                showImage(imageView, imagePath);            }            @Override            public void throwError(Exception e) {                e.printStackTrace();            }        });        selectPictureManager.showSelectPicturePopupWindow(parent_view);    }    /**     * 展现图片     *     * @param path     */    void showImage(ImageView imageView, String path) {        ImageLoadBaseTool.display(this.getContext(), imageView, path, new ImageConfig(R.mipmap.ic_launcher, R.mipmap.ic_launcher_round, 25), null);    }    @Override    public void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        selectPictureManager.onActivityResult(requestCode, resultCode, data);    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        selectPictureManager.onRequestPermissionsResult(requestCode, permissions, grantResults);    }}

可以看出,通过继承SimpleBaseFragment,我们的MainFragment只要要关注具体的业务解决就可,不用再关注导航栏、错误界面等逻辑。

上面实现了基本的Fragment封装,接下来详情承载Fragment的Activity:
先定义一个基本的Activity,这个是为了便于写统计、埋点等代码:BaseActivity

/** * 基础的activi 其余少量自己设置的activity也需要继承该类 可以在这里面增加界面统计、埋点等基础代码 * TestApplication * Created by anonyper on 2018/12/18. */public class BaseActivity extends AppCompatActivity {    public String TAG = this.getClass().getName();    @Override    protected void onResume() {        super.onResume();    }    @Override    protected void onDestroy() {        super.onDestroy();    }}

而后通过继承这个基本的Activity,同时增加对Fragment的承载:
SimpleBaseActivity.java

/** * 基本的activi实现类 项目的fragment都承载在这个类中 * TestApplication * Created by anonyper on 2018/12/18. */public class SimpleBaseActivity extends BaseActivity {    BaseFragment baseFragment;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.view_sample_activity);        if (getIntent().getStringExtra(BaseConfig.FRAGMENT_KEY) != null) {            showFragment();        }    }    void showFragment() {        Intent intent = getIntent();        if (intent == null) {            return;        }        String targetFragment = intent.getStringExtra(BaseConfig.FRAGMENT_KEY);        try {            baseFragment = (BaseFragment) Class.forName(targetFragment).newInstance();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        if (baseFragment == null) {            LogUtil.e(TAG, "targetFragment error");            return;        }        Bundle bundle = intent.getExtras();        if (bundle != null) {            baseFragment.setArguments(bundle);        }        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();        ft.replace(R.id.fragment_container, baseFragment, this.getClass().getName());        ft.commitAllowingStateLoss();    }    /**     * 权限的回调也通知fragment 防止部分fragment需要获取隐私权限     * @param requestCode     * @param permissions     * @param grantResults     */    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        if (baseFragment != null) {            baseFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);        }    }    /**     * 通知fragment     * @param requestCode     * @param resultCode     * @param data     */    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        if (baseFragment != null) {            baseFragment.onActivityResult(requestCode, resultCode, data);        }    }}

其中R.layout.view_sample_activity布局文件如下:

<?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="match_parent"    android:background="@color/app_bg_theme"    android:orientation="vertical">    <FrameLayout        android:id="@+id/fragment_container"        android:layout_width="fill_parent"        android:layout_height="fill_parent" /></LinearLayout>

Manifast.xml配置如下:

<activity            android:name="com.kotlin.anonyper.testapplication.activity.SimpleBaseActivity"            android:configChanges="orientation|screenSize|keyboardHidden"            android:label="@string/app_name">            <intent-filter>                <action android:name="com.kotlin.anonyper.testapplication.sample.action" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </activity>

而后使用的地方,同时Intent发送com.kotlin.anonyper.testapplication.sample.action这个action来启动SimpleBaseActivity。具体如下:

Intent intent = new Intent();        intent.putExtra(BaseConfig.FRAGMENT_KEY, MainFragment.class.getName());        intent.setAction(BaseConfig.ACTION);        startActivity(intent);

其中BaseConfig.FRAGMENT_KEYBaseConfig.ACTION是:

public static final String FRAGMENT_KEY = "base_fragment";public static final String ACTION = "com.kotlin.anonyper.testapplication.sample.action";

以上代码,就完成了客户界面View的封装,这样以后在写代码的过程中,只要要关注layout布局文件以及对饮的业务逻辑,其余的就自动完成。当然,这个配合网络数据加载过程使用才会更有意义,后续会在这个基础上,加上网络请求的使用过程,让两者联动起来!
代码下载地址:https://download.csdn.net/download/she_cool_/10861278

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android框架思考–界面View封装

发表回复