Android UI框架快速搭建实践

转载请注明原作者,如果你觉得这篇文章对你有帮助或启发,可以关注打赏。

Android UI框架快速搭建实践_第1张图片
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 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上了,感兴趣的同学可以去看看。

你可能感兴趣的:(Android UI框架快速搭建实践)