Android 进阶——以一种较为优雅的小框架替代普通回调实现Fragment和Activity之间的通信
引言
前面两篇文章设计模式——面向对象进阶之面向接口再抽象实现通用的接口框架(一)和设计模式——面向对象进阶之面向接口再抽象实现通用的接口框架(二)介绍了个自己封装的一套接口小框架,目的是取代普通回调的常规方式,以期以较优雅的方式来替代常规的接口回调,具体思想和核心代码都再上面两篇文章做了详细介绍,这篇文章就直接使用那套小框架实现Fragment和Activity之间通信。
一、底部导航栏控件BottomNavigationView
在使用那套小框架前,先插入一些底部导航栏BottomNavigationView的简单介绍,BottomNavigationView是Material Design Components 推出的位于support.design 包的官方Material Design 风格的底部导航栏控件,提供不多于 5 个菜单的底部导航栏实现(BottomNavigationView 只支持 3 到 5 个子菜单数量的导航栏。并且,考虑到用户体验,3 个及3个以下菜单数量的导航栏,与超过 3 个时,交互过程也有所区分。关于最多支持 5 个字菜单的内容,可以从 BottomNavigationView 源码中查看:public static final int MAX_ITEM_COUNT = 5;当超出这个数量时,产生非法参数异常)。
1、BottomNavigationView的使用
1.1、首先引入对应的库
implementation 'com.android.support:design:26.1.0'
1.2、在布局文件中声明定义BottomNavigationView
app:menu ——引用的menu资源布局文件名
app:itemIconTint——Icon 图标着色,值为一个 ColorStateList ,可以在 color 资源文件夹中定义。使用这个属性,奇妙利用 tint 着色器实现一个图标多种状态下使用
app:itemTextColor——Label 文字颜色定义
app:itemBackground——背景内容
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.crazymo.universallinteface.MainActivity">
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_nav_main"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="@android:color/black"
android:layout_alignParentBottom="true"
app:itemIconTint="@color/colorstate_bottom_item_color"
app:itemTextColor="@color/colorstate_bottom_item_color"
app:itemBackground="@android:color/black"
app:menu="@menu/menu_bottomview_main"/>
</RelativeLayout>
1.3、在menu下的定义item的布局文件
使用 menu 资源定义菜单内容,所以这里的item对应的xml布局文件一定要在res/menu/ 下建立
<!--res/menu/menu_bottomview_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item
android:id="@+id/id_bottomv_home"
android:enabled="true"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_bottomview_home"
android:title="home" />
<item
android:id="@+id/id_bottomv_purchase"
android:enabled="true"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_bottomview_purchase"
android:title="purchase" />
<item
android:id="@+id/id_bottomv_more"
android:enabled="true"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_bottomview_more"
android:title="more" />
</menu>
1.4、在Activity中初始化并监听相应事件
通过 setOnNavigationItemSelectedListener() 方法可以监听不同子菜单的选中切换事件
mBottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
}
return false;
}
});
二、使用通用接口小框架实现Fragment和Activity之间的通信
- 引入接口小框架,具体参见前两篇文章设计模式——面向对象进阶之面向接口再抽象实现通用的接口框架(一)和设计模式——面向对象进阶之面向接口再抽象实现通用的接口框架(二)。
- 定义”接口”,其实就是定义一个字符串
- 实现接口方法逻辑
- 绑定接口
- 调用接口方法
package com.crazymo.universallinteface;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.FrameLayout;
import android.widget.Toast;
import java.util.ArrayList;
import crazymo.core.FunctionManager;
import crazymo.core.FunctionWithParamNoResult;
public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener,
MainFragment.OnFragmentListener{
private static final int FRAGMENT_TAG_MAIN = 0;
private static final int FRAGMENT_TAG_MORE =2 ;
private static final int FRAGMENT_TAG_PURCHASE = 1;
private FrameLayout mLayout;
private BottomNavigationView mBottomNavigationView;
private ArrayList<Fragment> mFragments = new ArrayList<>();
private Fragment mCurrFragment;
private int currIndex = 0;
private MainFragment mMainFragment=new MainFragment();
private MoreFragment mMoreFragment=new MoreFragment();
private PurchaseFragment mPurchaseFragment=new PurchaseFragment();
private FunctionManager mManager=FunctionManager.getInstance();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
getViews();
mBottomNavigationView.setOnNavigationItemSelectedListener(this);
initFragment();
changeFragment(0);
}
private void getViews() {
mLayout = findViewById(R.id.fragment_container);
mBottomNavigationView = findViewById(R.id.bottom_nav_main);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.id_bottomv_home:
changeFragment(0);
return true;
case R.id.id_bottomv_purchase:
changeFragment(1);
return true;
case R.id.id_bottomv_more:
changeFragment(2);
return true;
default:
break;
}
return false;
}
private void initFragment() {
mFragments.add(mMainFragment);
mFragments.add(mPurchaseFragment);
mFragments.add(mMoreFragment);
}
private void changeFragment(int position) {
switch (position) {
case FRAGMENT_TAG_MAIN:
if (mMainFragment == null) {
mMainFragment = (MainFragment) mFragments.get(0);
}
chooseFragment(mMainFragment);
currIndex = 0;
break;
case FRAGMENT_TAG_PURCHASE:
if (mPurchaseFragment == null) {
mPurchaseFragment = (PurchaseFragment) mFragments.get(2);
}
chooseFragment(mPurchaseFragment);
currIndex = 1;
break;
case FRAGMENT_TAG_MORE:
if (mMoreFragment == null) {
mMoreFragment = (MoreFragment) mFragments.get(1);
}
chooseFragment(mMoreFragment);
currIndex = 2;
break;
default:
break;
}
}
private void chooseFragment(Fragment fragment) {
if (mCurrFragment == fragment) {
return;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (fragment.isAdded()) {
transaction.show(fragment);
} else {
transaction.add(R.id.fragment_container, fragment,fragment.getClass().getName());
}
if (mCurrFragment != null) {
transaction.hide(mCurrFragment);
}
transaction.commit();
mCurrFragment = fragment;
hideFragments();
}
private void hideFragments() {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (mFragments != null) {
for (int i = 0; i < mFragments.size() - 1; i++) {
if (currIndex != i) {
if (!(mFragments.get(i).isHidden())) {
transaction.hide(mFragments.get(i));
} else {
continue;
}
}
}
}
}
@Override
public void onFragementListen(String pStr) {
Toast.makeText(this,pStr,Toast.LENGTH_LONG).show();
}
//2、在Activity中实现接口方法逻辑
public void implFragmentCallback(String tag){
if(PurchaseFragment.class.getName().equals(tag)){
FunctionWithParamNoResult<String> withParamNoResult=new FunctionWithParamNoResult<String>(PurchaseFragment.FRAGMENTLISTEN) {
@Override
public void function(String pS) {
Toast.makeText(MainActivity.this,""+pS,Toast.LENGTH_SHORT).show();
}
};
mManager.addFunciton(withParamNoResult);
}else if(MoreFragment.class.getName().equals(tag)){
}
}
}
PurchaseFragment通过接口小框架与Activity实现通信:
package com.crazymo.universallinteface;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import crazymo.core.FunctionManager;
public class PurchaseFragment extends Fragment {
public final static String FRAGMENTLISTEN="PurchaseFragment.OnFragmentListener";//1、定义接口
private Button btn;
private FunctionManager mManager=FunctionManager.getInstance();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_purchase, container, false);
init(view);
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
//2、绑定接口并调用实现接口方法
if(context instanceof MainActivity){
((MainActivity)context).implFragmentCallback(getTag());
}
}
@Override
public void onDetach() {
super.onDetach();
}
private void init(View pView) {
btn=pView.findViewById(R.id.btn_click);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//3、调用接口方法
mManager.invokeFunc(FRAGMENTLISTEN,String.class,"我是从MainFragment传递到Activity的数据");
}
});
}
}
由于是需要与MainActivity通信所以在Activity中实现接口中的方法
public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener,
MainFragment.OnFragmentListener{
//4、实现PurchaseFragment的接口方法
public void implFragmentCallback(String tag){
if(PurchaseFragment.class.getName().equals(tag)){
FunctionWithParamNoResult<String> withParamNoResult=new FunctionWithParamNoResult<String>(PurchaseFragment.FRAGMENTLISTEN) {
@Override
public void function(String pS) {
Toast.makeText(MainActivity.this,""+pS,Toast.LENGTH_SHORT).show();
}
};
mManager.addFunciton(withParamNoResult);
}
}
}
三、使用常规的回调接口实现Fragment和Activity之间的通信
MainFragment通过常规的方式实现与MainActivity通信
//1、implement MainFragment的回调接口
public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener,
MainFragment.OnFragmentListener{
private static final int FRAGMENT_TAG_MAIN = 0;
private static final int FRAGMENT_TAG_MORE =2 ;
private static final int FRAGMENT_TAG_PURCHASE = 1;
private FrameLayout mLayout;
private BottomNavigationView mBottomNavigationView;
private ArrayList<Fragment> mFragments = new ArrayList<>();
private Fragment mCurrFragment;
private int currIndex = 0;
private MainFragment mMainFragment=new MainFragment();
private MoreFragment mMoreFragment=new MoreFragment();
private PurchaseFragment mPurchaseFragment=new PurchaseFragment();
private FunctionManager mManager=FunctionManager.getInstance();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
getViews();
mBottomNavigationView.setOnNavigationItemSelectedListener(this);
initFragment();
changeFragment(0);
}
private void getViews() {
mLayout = findViewById(R.id.fragment_container);
mBottomNavigationView = findViewById(R.id.bottom_nav_main);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.id_bottomv_home:
changeFragment(0);
return true;
case R.id.id_bottomv_purchase:
changeFragment(1);
return true;
case R.id.id_bottomv_more:
changeFragment(2);
return true;
default:
break;
}
return false;
}
private void initFragment() {
mFragments.add(mMainFragment);
mFragments.add(mPurchaseFragment);
mFragments.add(mMoreFragment);
}
private void changeFragment(int position) {
switch (position) {
case FRAGMENT_TAG_MAIN:
if (mMainFragment == null) {
mMainFragment = (MainFragment) mFragments.get(0);
}
chooseFragment(mMainFragment);
currIndex = 0;
break;
case FRAGMENT_TAG_PURCHASE:
if (mPurchaseFragment == null) {
mPurchaseFragment = (PurchaseFragment) mFragments.get(2);
}
chooseFragment(mPurchaseFragment);
currIndex = 1;
break;
case FRAGMENT_TAG_MORE:
if (mMoreFragment == null) {
mMoreFragment = (MoreFragment) mFragments.get(1);
}
chooseFragment(mMoreFragment);
currIndex = 2;
break;
default:
break;
}
}
private void chooseFragment(Fragment fragment) {
if (mCurrFragment == fragment) {
return;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (fragment.isAdded()) {
transaction.show(fragment);
} else {
transaction.add(R.id.fragment_container, fragment,fragment.getClass().getName());
}
if (mCurrFragment != null) {
transaction.hide(mCurrFragment);
}
transaction.commit();
mCurrFragment = fragment;
hideFragments();
}
private void hideFragments() {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (mFragments != null) {
for (int i = 0; i < mFragments.size() - 1; i++) {
if (currIndex != i) {
if (!(mFragments.get(i).isHidden())) {
transaction.hide(mFragments.get(i));
} else {
continue;
}
}
}
}
}
@Override
public void onFragementListen(String pStr) {
//2、继承常规接口实现回调方法
Toast.makeText(this,pStr,Toast.LENGTH_LONG).show();
}
}
在MainFragment重新声明一个回调接口,并且初始化,使用结束之后再进行手动回收避免内存泄漏
public class MainFragment extends Fragment implements View.OnClickListener {
private OnFragmentListener mListener;
private Button btn;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_home, container, false);
init(view);
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
//3、绑定回调接口
if (context instanceof OnFragmentListener) {
mListener = (OnFragmentListener) context;
} else {
throw new RuntimeException(context.toString()+ "must implement MainFragment.OnFragmentListener");
}
}
@Override
public void onDetach() {
super.onDetach();
//5、销毁回调防止内存泄漏
mListener=null;
}
private void init(View view){
btn=view.findViewById(R.id.btn_click);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
//4、调用回调方法
mListener.onFragementListen("我是从MainFragment传递到Activity的数据");
}
public interface OnFragmentListener{
void onFragementListen(String pStr);
}
}
对应的定义布局xml文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.crazymo.universallinteface.MainActivity">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_nav_main"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="@android:color/black"
android:layout_alignParentBottom="true"
app:itemIconTint="@color/colorstate_bottom_item_color"
app:itemTextColor="@color/colorstate_bottom_item_color"
app:itemBackground="@android:color/black"
app:menu="@menu/menu_bottomview_main"/>
</RelativeLayout>
fragment_home.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="36sp"
android:text="Home Page"/>
<ImageView
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:src="@mipmap/cmo2"/>
<Button
android:id="@+id/btn_click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="doClick"/>
</LinearLayout>
fragment_purchase.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="36sp"
android:text="PurchaseCar Page"/>
<Button
android:id="@+id/btn_click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="doClick(有参数无返回值)"/>
</LinearLayout>
四、通用接口小框架 VS 传统回调接口形式
为了更直观体现我对于两种方式的具体步骤做了个简单的对比表