ui框架搭建.gif
如上图,本文主要讲解2点 (mvp和dagger2不是本文重点):
基类的抽取和封装(mvp+Dagger2)
如何使用ViewPager+TabLayout快速搭建ios风格的多个底部导航栏的主页框架
别问我为什么不来个MaterialDesign风格的,说起来都是泪,我个人是喜欢MD的,可以给用户更清爽更有层次的视觉感受,但致命问题就是她直接呈现给用户的信息少了,与当前国内的关注点有冲突,很多产品不懂MD,Android的界面开发也全按IOS来的,设计图都是一套这种事我是不会告诉你的。
代码最直观,我就直接上代码了。ps:为节省大家时间,本文只会展示核心代码。
BaseActivity
package com.example.arron.demo.view.base; import android.support.v7.app.AppCompatActivity; import android.view.KeyEvent; import com.example.arron.demo.internal.di.modules.ActivityModule; import com.example.arron.demo.view.navigation.Navigator; import javax.inject.Inject; /** * Created by Arron on 16/6/28. */ public abstract class BaseActivity extends AppCompatActivity { //使用Dagger2注入的全局导航类 @Inject public Navigator navigator; //动态获取类名 打印日志使用 protected String TAG = this.getClass().getSimpleName(); //布局文件ID protected abstract int getContentViewId(); /** * 布局中Fragment的ID * 如果没有fragment则不必实现 */ protected abstract int getFragmentContentId(); //添加fragment protected void addFragment(BaseFragment fragment) { if (fragment != null) { getSupportFragmentManager().beginTransaction() .replace(getFragmentContentId(), fragment, fragment.getClass().getSimpleName()) .addToBackStack(fragment.getClass().getSimpleName()) .commitAllowingStateLoss(); } } //移除fragment protected void removeFragment() { if (getSupportFragmentManager().getBackStackEntryCount() > 1) { getSupportFragmentManager().popBackStack(); } else { finish(); } } //返回键返回事件的处理 //如果FragmentStack中只有1个fragment 关闭当前activity // 如果FragmentStack中还有>1数量fragment则可以removeFragment()将fragment出栈 此部分交给子类实现 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (KeyEvent.KEYCODE_BACK == keyCode) { if (getSupportFragmentManager().getBackStackEntryCount() == 1) { finish(); return true; } } return super.onKeyDown(keyCode, event); } @Override protected void onStop() { super.onStop(); } //配合Dagger2使用 返回当前Activity的ActivityModule对象 // ActivityModule生命周期与activity是绑定的 protected ActivityModule getActivityModule() { return new ActivityModule(this); } }
AppActivity
package com.example.arron.demo.view.base; import android.content.Intent; import android.os.Bundle; import com.example.arron.demo.AndroidApplication; /** * Created by Arron on 16/6/29. */ public abstract class AppActivity extends BaseActivity { /** * 获取第一个fragment 如果没有返回null即可 */ protected abstract BaseFragment getFirstFragment(); /** * 处理Intent * * @param intent */ protected void handleIntent(Intent intent) { } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getContentViewId()); AndroidApplication.getComponent().inject(this); if (null != getIntent()) { handleIntent(getIntent()); } initView(); initData(); //避免重复添加Fragment if (null == getSupportFragmentManager().getFragments()) { BaseFragment firstFragment = getFirstFragment(); if (null != firstFragment) { addFragment(firstFragment); } } } /** * 初始化data */ protected abstract void initData(); /** * 初始化view */ protected abstract void initView(); }
HomeActivity
package com.example.arron.demo.view.activity; import android.graphics.drawable.Drawable; import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.widget.TextView; import com.example.arron.demo.R; import com.example.arron.demo.utils.ResourceUtils; import com.example.arron.demo.view.adapter.HomeFragmentAdapter; import com.example.arron.demo.view.base.AppActivity; import com.example.arron.demo.view.base.BaseFragment; import butterknife.Bind; import butterknife.ButterKnife; /** * Created by Arron on 16/6/29. */ public class HomeActivity extends AppActivity { @Bind(R.id.home_content) ViewPager container; @Bind(R.id.tab) TabLayout tab; private HomeFragmentAdapter adapter; @Override protected BaseFragment getFirstFragment() { return null; } @Override protected void initData() { } @Override protected void initView() { ButterKnife.bind(this); tab.setTabMode(TabLayout.MODE_FIXED); initTab(); setListener(); setAdapterAndNotify(); container.setOffscreenPageLimit(3); } private void setAdapterAndNotify() { if (null == adapter) { adapter = new HomeFragmentAdapter(getSupportFragmentManager(), 4); container.setAdapter(adapter); } else { adapter.notifyDataSetChanged(); } } private void setListener() { //这行代码将TabLayout与ViewPager的页面切换绑定 原理很简单 看源码 container.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tab)); tab.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { int position = tab.getPosition(); //ViewPager切换页面无动画需要使用两个参数的方法并传入false container.setCurrentItem(position, false); //这句别忘了 否则tab就丢失选择器效果了 tab.getCustomView().setEnabled(true); //当前页面的数据加载 adapter.getItem(position).loadData(); } @Override public void onTabUnselected(TabLayout.Tab tab) { //别忘了 tab.getCustomView().setEnabled(false); } @Override public void onTabReselected(TabLayout.Tab tab) { } }); } //为了达到切换tab文字和icon同步变色 这里给TextView设置选择器使用Enabled属性切换 //icon同理 private void initTab() { LayoutInflater inflater = getLayoutInflater(); TextView view; for (int i = 0; i < 4; i++) { view = (TextView) inflater.inflate(R.layout.tab_home_item, null); String text = null; Drawable drawable = null; switch (i) { case 0: text = ResourceUtils.getString(R.string.tab_main); view.setEnabled(true); drawable = ResourceUtils.getDrawable(R.drawable.tab_main); break; case 1: text = ResourceUtils.getString(R.string.tab_what); drawable = ResourceUtils.getDrawable(R.drawable.tab_what); break; case 2: text = ResourceUtils.getString(R.string.tab_message); drawable = ResourceUtils.getDrawable(R.drawable.tab_message); break; case 3: text = ResourceUtils.getString(R.string.tab_mine); drawable = ResourceUtils.getDrawable(R.drawable.tab_mine); break; } view.setText(text); drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight()); view.setCompoundDrawables(null, drawable, null, null); TabLayout.Tab tab = this.tab.newTab().setCustomView(view); this.tab.addTab(tab, i == 0 ? true : false); } } @Override protected int getContentViewId() { return R.layout.activity_home; } @Override protected int getFragmentContentId() { return 0; } }
BaseFragment
package com.example.arron.demo.view.base; import android.app.Activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.example.arron.demo.presenter.Presenter; import com.example.arron.demo.view.BaseView; import com.example.arron.demo.view.loading.VaryViewHelperController; import com.trello.rxlifecycle.components.support.RxFragment; import java.util.List; import butterknife.ButterKnife; /** * Created by Arron on 16/6/29. */ public abstract class BaseFragment<T extends Presenter> extends RxFragment implements BaseView { //与Fragment绑定的activity对象 protected BaseActivity mActivity; //当前View的Presenter protected T mPresenter; private View contentView; //通用loading页error页等的控制器 private VaryViewHelperController mVaryViewHelperController; protected abstract void initView(View view, Bundle savedInstanceState); /** * 初始化数据 页面加载完毕调用 */ protected abstract void initData(); /** * 切换到页面需要重新加载数据的实现此方法 */ public abstract void loadData(); //获取布局文件ID protected abstract int getLayoutId(); //获取宿主Activity protected BaseActivity getHoldingActivity() { return mActivity; } @Override public void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = (BaseActivity) activity; } //添加fragment protected void addFragment(BaseFragment fragment) { if (null != fragment) { getChildFragmentManager().beginTransaction() .replace(getFragmentContentId(), fragment, fragment.getClass().getSimpleName()) .addToBackStack(fragment.getClass().getSimpleName()) .commitAllowingStateLoss(); } } //移除fragment protected void removeFragment() { if (getChildFragmentManager().getBackStackEntryCount() > 1) { getChildFragmentManager().popBackStack(); } } //添加fragment的布局节点的ID protected abstract int getFragmentContentId(); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (contentView == null) { contentView = inflater.inflate(getLayoutId(), container, false); initView(contentView, savedInstanceState); } else { ViewGroup parent = (ViewGroup) contentView.getParent(); if (parent != null) { parent.removeView(contentView); } } if (null == mVaryViewHelperController) mVaryViewHelperController = new VaryViewHelperController(getLoaingTargetView()); if (null == mPresenter) mPresenter = getChildPresenter(); return contentView; } protected abstract T getChildPresenter(); protected abstract View getLoaingTargetView(); @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); initData(); } @Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); if (null != this.getView()) { this.getView().setVisibility(menuVisible ? View.VISIBLE : View.INVISIBLE); } } @Override public void onResume() { super.onResume(); if (null != mPresenter) mPresenter.resume(); } @Override public void onPause() { super.onPause(); if (null != mPresenter) mPresenter.pause(); } @Override public void onDestroy() { super.onDestroy(); ButterKnife.unbind(this); if (null != mPresenter) mPresenter.destroy(); } @Override public BaseActivity getContext() { return mActivity; } @Override public void onSaveInstanceState(Bundle outState) { //Google bug outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE"); super.onSaveInstanceState(outState); } @Override public void showLoading() { if (mVaryViewHelperController == null) { throw new IllegalStateException("no ViewHelperController"); } mVaryViewHelperController.showLoading(); } @Override public void refreshView() { if (mVaryViewHelperController == null) { throw new IllegalStateException("no ViewHelperController"); } mVaryViewHelperController.restore(); } @Override public void showNetError() { if (mVaryViewHelperController == null) { throw new IllegalStateException("no ViewHelperController"); } mVaryViewHelperController.showNetworkError(v -> { showLoading(); mPresenter.requestData(getRequestParams()); }); } @Override public void hasNoMoreData() { } @Override public void loadMoreFinish(List dates) { } @Override public void showRefreshFinish(List score) { } @Override public void showToastError() { } protected String getRequestParams() { return null; } @Override public void showEmptyView(String msg) { if (mVaryViewHelperController == null) { throw new IllegalStateException("no ViewHelperController"); } mVaryViewHelperController.showEmpty(msg); } }
eg:具体页面的Fragment
package com.example.arron.demo.view.fragment; import android.os.Bundle; import android.view.View; import com.example.arron.demo.R; import com.example.arron.demo.presenter.Presenter; import com.example.arron.demo.view.base.BaseFragment; /** * Created by Arron on 16/6/29. */ public class MineFragment extends BaseFragment { @Override protected void initView(View view, Bundle savedInstanceState) { } @Override protected void initData() { } @Override public void loadData() { } @Override protected int getLayoutId() { return R.layout.fragment_mine; } @Override protected int getFragmentContentId() { return 0; } @Override protected Presenter getChildPresenter() { return null; } @Override protected View getLoaingTargetView() { return null; } }
View层使用到的VaryViewHelperController
package com.example.arron.demo.view.loading; import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.example.arron.demo.R; public class VaryViewHelperController { private IVaryViewHelper helper; public VaryViewHelperController(View view) { this(new VaryViewHelper(view)); } public VaryViewHelperController(IVaryViewHelper helper) { super(); this.helper = helper; } public void showNetworkError(View.OnClickListener onClickListener) { View layout = helper.inflate(R.layout.pager_error); Button againBtn = (Button) layout.findViewById(R.id.pager_error_loadingAgain); if (null != onClickListener) { againBtn.setOnClickListener(onClickListener); } helper.showLayout(layout); } public void showEmpty(String emptyMsg) { View layout = helper.inflate(R.layout.page_no_data); TextView textView = (TextView) layout.findViewById(R.id.tv_no_data); if (!TextUtils.isEmpty(emptyMsg)) { textView.setText(emptyMsg); } helper.showLayout(layout); } public void showLoading() { View layout = helper.inflate(R.layout.pager_loading); helper.showLayout(layout); } public void restore() { helper.restoreView(); } }
VaryViewHelperController中使用到的添加移除view的工具类IVaryViewHelper
package com.example.arron.demo.view.loading; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * VaryViewHelper可以方便添加或移除view */ public class VaryViewHelper implements IVaryViewHelper { private View view; private ViewGroup parentView; private int viewIndex; private ViewGroup.LayoutParams params; private View currentView; public VaryViewHelper(View view) { super(); this.view = view; } private void init() { params = view.getLayoutParams(); if (view.getParent() != null) { parentView = (ViewGroup) view.getParent(); } else { parentView = (ViewGroup) view.getRootView().findViewById(android.R.id.content); } int count = parentView.getChildCount(); for (int index = 0; index < count; index++) { if (view == parentView.getChildAt(index)) { viewIndex = index; break; } } currentView = view; } @Override public View getCurrentLayout() { return currentView; } @Override public void restoreView() { showLayout(view); } @Override public void showLayout(View view) { if (parentView == null) { init(); } this.currentView = view; if (parentView.getChildAt(viewIndex) != view) { ViewGroup parent = (ViewGroup) view.getParent(); if (parent != null) { parent.removeView(view); } parentView.removeViewAt(viewIndex); parentView.addView(view, viewIndex, params); } } @Override public View inflate(int layoutId) { return LayoutInflater.from(view.getContext()).inflate(layoutId, null); } @Override public Context getContext() { return view.getContext(); } @Override public View getView() { return view; } }
That's all!我个人更喜欢看代码,所以没有大段的文字,但关键点都在代码中注释了,如果有不明白的地方,欢迎提问。
应大家的要求将代码分享到GitHub上了,感兴趣的同学可以去看看。
作者:困境中的扳道工