Android_碎片01_Fragment的使用(手机平板要兼顾)

  屏幕大小差距过大有可能会让同样的界面在视觉效果上有较大的差异,比如一些界面在手机上看起来非常美观,但在平板电脑上看起来就可能会有控件被过分拉长、元素之间空隙过大等情况。

一、碎片是什么:

Fragment(碎片)是一种必须嵌入在 Activity 中使用的UI片段,让程序更加充分合理地利用大屏幕的空间,广泛应用于平板。

如果把手机的设计方案用在平板上,那么新闻标题列表将会被拉长至填充满整个平板的屏幕,而新闻的标题一般都不会太长,这样会导致界面上有大量的空白区域。

更好的设计方案是将新闻标题列表界面和新闻详细内容界面分别放在两个碎片中,然后在同一个活动里引入这两个碎片,这样就可以充分利用屏幕空间。

二、碎片的使用

将Fragment加载到Activity当中有两种方式:

  • 方式一:在 activity 布局文件中添加 fragment
  • 方式二:在 activity 代码中动态添加 fragment

第一种方式虽然简单但灵活性不够。添加Fragment到Activity的布局文件当中,就等同于将Fragment及其视图与activity的视图绑定在一起,且在activity的生命周期过程中,无法切换fragment视图。

第二种方式比较复杂,但也是唯一一种可以在运行时控制fragment的方式加载、移除、替换)。

1、在 activity 布局文件中添加 fragment

简单示例:一个活动中添加两个碎片,并让这两个碎片平分活动空间。

左侧碎片布局 fragment_left.xml:

<?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:orientation="vertical">

    <Button
        android:id="@+id/btn_leftFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button" />

</LinearLayout>

右侧碎片布局 fragment_right.xml:

<?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="#00ff00"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="This is right fragment"
        android:textSize="20dp" />

</LinearLayout>

布局文件对应的Java类,让它们继承 Fragment 并重写 onCreateView() 方法:

  有两个不同包下的 Fragment :

    系统内置的 android.app.Fragment :

    support-v4 库中的 android.support.v4.app.Fragment :

  区别

    比如在Fragment 中嵌套使用 Fragment ,该功能在 Android 4.2 系统中才开始支持,若使用内置的 Fragment,运行在4.2 系统之前的设备上程序会崩溃。而使用 support-v4 库就可以了。

    另外,不需要在 build.gradle 文件中添加 support-v4 库的依赖,因为 build.gradle 文件添加的 appcompat-v7 库会将 support-v4库一起引入。

public class LeftFragment extends Fragment {
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // 加载布局文件
     return inflater.inflate(R.layout.fragment_left, container, false); } } ----------------------------------------------------------------------------- public class RightFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_right, container, false); } }

activity_main.xml :

使用 <fragment /> 标签在布局中添加碎片,使用 android:name 属性显式指明要添加的碎片类名(全路径类名)。
<?
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:orientation="horizontal"> <fragment android:id="@+id/left_fragment" android:name="com.company.test.LeftFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/right_fragment" android:name="com.company.test.RightFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>

 运行效果如图:

正如期待的一样,两个碎片平分了整个活动的布局。不过例子太简单,很难有实际应用,往下看 

2、在 activity 代码中动态添加 fragment 

动态添加碎片可以将程序界面定制得更加多样化。

继续新建 fragment_another_right.xml :

<?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="#ffff00"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="This is another right fragment"
        android:textSize="20dp" />
</LinearLayout>

对应的Java类 AnotherRightFragment :

public class AnotherRightFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_another_right, container, false);
    }
}

如何将它动态地添加到活动中?修改 activity_main.xml ,将右侧碎片替换成一个 FrameLayout ,该布局所有控件默认摆放在布局的左上角。由于这里仅需在布局里放入一个碎片,不需要任何定位,因此非常适合使用 FrameLayout 。代码如下:

<?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:orientation="horizontal">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.company.test.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/fl_right_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1">
    </FrameLayout>
</LinearLayout>

MainActivity :

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_leftFragment).setOnClickListener(this);
        replaceFragment(new RightFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_leftFragment:
                // 点击左侧碎片中按钮时,右侧碎片替换成 AnotherRightFragment
                replaceFragment(new AnotherRightFragment());
                break;
            default:
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
     // 第一个参数:容器 id;第二个参数:碎片实例; transaction.replace(R.id.fl_right_layout, fragment); transaction.commit(); } }

replaceFragment(Fragment fragment) 方法里用 getSupportFragmentManager() 来获取 FragmentManager ,以下是两个包中具有相同功能的方法名区别:

package android.app;
public class Activity {
    public FragmentManager getFragmentManager() {
        return mFragments.getFragmentManager();
    }
}


package android.support.v4.app;
public class FragmentActivity {
    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }
}

总结:动态添加碎片主要分为5步

  (1)创建待添加的碎片实例。

  (2)获取 FragmentManager ,在 Activity 中可以直接通过 getSupportFragmentManager() 方法得到。

  (3)开启一个事务,通过 beginTransaction() 方法开启。

  (4)向容器内添加或替换碎片,一般使用 replace() 方法实现,需要传入碎片容器的 id 和待添加的碎片实例

  (5)提交事务,调用 commit() 方法。

3、在碎片中模拟返回栈

模仿返回栈的效果,按下 Back 键回到上一个碎片。FragmentTransaction 中提供了一个 addToBackStack() 方法,可以用于将一个事务添加到返回栈中,修改 MainActivity 中的代码,如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        ...
        private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fl_right_layout, fragment);
        // public abstract FragmentTransaction addToBackStack(@Nullable String name);
        // 接收一个名字用于描述返回栈的状态,一般传入 null 即可。
        transaction.addToBackStack(null);
        transaction.commit();
    }
}

重新运行程序,点击按钮将 AnotherRightFragment 添加到活动中。按下 Back 键,回到 RightFragment 界面;继续按 Back 键,RightFragment 界面消失,再次按 Back 键,程序退出。

4、碎片和活动之间的通信

碎片和活动各自存在于一个独立的类。若想在活动中调用碎片里的方法,或者在碎片中调用活动里的方法,该如何实现呢?

1)在Activity中调用Fragment中的方法

在 activity 布局文件中添加 fragment 的情况。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RightFragment rightFragment = (RightFragment) getSupportFragmentManager().findFragmentById(R.id.right_fragment); // 参数:碎片的id,即布局下的 <fragment /> 标签的id
        // 下面就可调用 RightFragment 中的方法了
    }
}

在 activity 代码中动态添加 fragment 的情况。

方案一:因为需要在 activity 代码中 new 一个 fragment ,所以只要将这个实例保存起来(强转),就可以调用 Fragment 中的方法,也可以操作该碎片中的控件。

方案二:但是有一个比较好的做法,就是在fragment里面定义一个回调接口,然后要求宿主activity实现这个接口。代码如下

fragment_left.xml

<?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:orientation="vertical" >

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10" >
        <requestFocus />
    </EditText>
</LinearLayout>

LeftFragment.java

public class LeftFragment extends Fragment {
    private EditText editText;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left, container, false);
        editText = (EditText) view.findViewById(R.id.editText1);
        return view;
    }

    // 接口回调
    public void getEditText(CallBack callBack) {
        String msg = editText.getText().toString();
//        callBack.getResult(msg);
        callBack.getFragment(this);
    }

    public interface CallBack {
        void getResult(String result); // 为宿主activity提供一个获取碎片中控件内容的回调方法
        void getFragment(Fragment fragment); // 为宿主activity提供一个获取碎片实例的回调方法
    }

    public void method() {
        Logger.d("fragment logger输出");
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/left"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:background="#CCCCCC"
        android:orientation="vertical">

    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:orientation="vertical">

        <EditText
            android:id="@+id/editText1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10">

            <requestFocus />
        </EditText>

        <Button
            android:id="@+id/btn_getFragmentValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获得Fragment的值" />
    </LinearLayout>
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final LeftFragment leftFragment = new LeftFragment();
        addFragment(leftFragment);

        findViewById(R.id.btn_getFragmentValue).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击按钮后,通过接口回调,操作fragment方法和控件。
                leftFragment.getEditText(new LeftFragment.CallBack() {
                    @Override
                    public void getResult(String result) {
                        // 在activity中操作fragment中的控件。获取 EditText 控件内容,并弹出吐司。
                        Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
                    }

                    // 调用碎片中的方法
                    @Override
                    public void getFragment(Fragment fragment) {
                        // 在activity中调用fragment中的方法
                        LeftFragment leftFragment = (LeftFragment) fragment;
                        leftFragment.method();
                        // 在activity中操作fragment中的控件
                        EditText editText = (EditText) leftFragment.getView().findViewById(R.id.editText1);
                        Toast.makeText(MainActivity.this, editText.getText().toString(), Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
    }

    // 动态添加 Fragment
    private void addFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 第一个参数:容器 id;第二个参数:碎片实例;
        transaction.add(R.id.left, fragment, "left");
        transaction.commit();
    }

    public void method(){
        Logger.d("activity logger输出");
    }
}

2)在Fragment中调用Activity中的方法

Fragment可以通过 getActivity() 方法来获得 Activity 实例,然后就可以调用一些例如 findViewById() 之类的方法。另外,当碎片中需要使用 Context 对象时,也可以使用 getActivity() 方法,因此获取到的活动本身就是一个 Context 对象。

但是注意调用 getActivity() 时,fragment 必须和 Activity 关联(attached to an activity),否则将会返回一个null。例如:

MainActivity mainActivity = (MainActivity) getActivity(); // 获取到 MainActivity 实例,就可以调用该 Activity 中的方法了。
Button btn_button = (Button) getActivity().findViewById(R.id.btn_button);

【实例】在Activity的EditText中输入一段文本,这个时候,点击Fragment中的按钮,让它弹出吐司,显示出对应的文本。

在  1)在Activity中调用Fragment中的方法  中的基础上修改:

fragment_left.xml 中添加 Button 控件

<?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:orientation="vertical">

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10">

        <requestFocus />
    </EditText>

    <Button
        android:id="@+id/btn_getActivityValue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="获得Activity的值" />
</LinearLayout>

LeftFragment.java 中添加 Button 监听

public class LeftFragment extends Fragment {
    private EditText editText;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left, container, false);
        editText = (EditText) view.findViewById(R.id.editText1);

        view.findViewById(R.id.btn_getActivityValue).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 若Fragment与宿主activity的控件id重复,则获取的是此Fragment的;不重复,则获取宿主activity的;
                EditText editText = (EditText) getActivity().findViewById(R.id.editText1); // 这个id是重复的,所以获取的是Fragment的EditText控件,而不是宿主activity的EditText控件。
                Toast.makeText(getActivity(), editText.getText().toString(), Toast.LENGTH_SHORT).show();

                MainActivity mainActivity = (MainActivity) getActivity();
                mainActivity.method();
            }
        });
        return view;
    }

    ...
}

3)Fragment与Fragment之间的通信

基本思路是:首先在一个Fragment中可以得到与它相关联的Activity,然后再通过这个Activity去获取另外一个Fragment的实例,这样就实现了不同Fragment之间的通信。

 

posted on 2017-12-04 18:18  JonSnows  阅读(387)  评论(0)    收藏  举报

导航