当前位置:   article > 正文

Fragment的应用,实现横竖切换并兼容平板_fragment 横屏显示

fragment 横屏显示

前言

Fragment的应用越来越多,除了一些常用的:标签切换,引导页,广告位等,目前我们项目中,现在一些大大小小的自定义的控件,也先封装在Fragment中,然后在通过Activity来显示和隐藏,这样也切切实实的到达Fragment复用的效果,Activity的代码也少了。比如一些dialog、列表框、自定义的音量条等。这篇主要是对Fragment的应用,如果对Fragment的基础还不是很熟悉的话,可以移步到这里,有详细的介绍。

开始

这篇也是对上一篇基础的加强,本篇介绍一个屏幕设配的栗子来解析,代码在最后也会给出。主要是在开发TV时,经常会有手机和电视端共用apk,那么就需要对屏幕的大小需要设配了。

该实例同时也是借鉴官方的例子,实现了屏幕横竖的切换适配,在最后再添加了Android pad大屏幕的支持。

                                                           

首先,一般第一步就是新建一个MainActivity,用于控制布局的显示。其布局文件activity_main.xml如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <fragment
  6. android:id="@+id/titles"
  7. android:name="com.gotechcn.fragmentdemo.TitleFragment"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent"/>
  10. </FrameLayout>

这里引入了一个TitleFragment,是用于显示标题布局,这个后面会介绍。这个布局默认用于显示竖屏时的布局文件,那么横屏的布局文件是什么样的?

在res目录下新建layout-land目录,然后这个目录下创建新的activity_main.xml,这里的名字是需要和刚才的文件名保持一致的。代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="horizontal">
  6. <fragment
  7. android:id="@+id/titles"
  8. android:name="com.gotechcn.fragmentdemo.TitleFragment"
  9. android:layout_width="0px"
  10. android:layout_height="match_parent"
  11. android:layout_weight="1"/>
  12. <FrameLayout
  13. android:id="@+id/details"
  14. android:layout_width="0px"
  15. android:layout_height="match_parent"
  16. android:layout_weight="2"
  17. android:background="?android:attr/detailsElementBackground"/>
  18. </LinearLayout>

这个布局和前面的布局,多了一个FrameLayout,这个布局是用来显示Detail的内容。当屏幕纵向显示时,系统会应用该布局。


现在,开始先来实现第一个Fragment:TitleFragment,具体的代码如下:


/**
  1. * 用于显示Title的布局
  2. *
  3. * 直接继承ListFragment
  4. * 可以少些一些代码,也可以继承Fragment,但就需要自己写ListView + 适配器了;
  5. * 继承了ListView,则可以不重写onCreateView()方法;
  6. */
  7. public class TitleFragment extends ListFragment
  8. {
  9. /**
  10. * 初始化化数据
  11. */
  12. private String [] mTitles = {"IPTV", "VOD", "YOUTUBE", "DVB", "KTV"};
  13. private Callbacks mCallbacks = null;
  14. @Override
  15. public void onAttach(Activity activity) {
  16. super.onAttach(activity);
  17. try {
  18. mCallbacks = (Callbacks) activity;
  19. } catch (ClassCastException e) {
  20. throw new ClassCastException(activity.toString() + " must implement Callbacks");
  21. }
  22. }
  23. @Override
  24. public void onActivityCreated(Bundle savedInstanceState)
  25. {
  26. super.onActivityCreated(savedInstanceState);
  27. //添加设配器,默认的布局,也可以自定义布局显示
  28. setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_activated_1, mTitles));
  29. //设置为单选模式
  30. getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
  31. }
  32. @Override
  33. public void onListItemClick(ListView l, View v, int position, long id)
  34. {
  35. super.onListItemClick(l, v, position, id);
  36. mCallbacks.onItemClick(position);
  37. }
  38. /**
  39. * item点击回调,在MainActivity实现该接口
  40. */
  41. public interface Callbacks
  42. {
  43. void onItemClick(int index);
  44. }
  45. public void setCallbacks(Callbacks callbacks){
  46. mCallbacks = callbacks;
  47. }
  48. }

代码很简单,简单说说,顺便补充一下知识点,主要有2个知识点:

1.如何实现ListFragment;

2.如何和Activity实现通信交互;


首先:

一般,TitleFragment是继承Fragment,但这里既然需要一个List的列表,那么就可以直接继承ListFragment,它已经帮我们准备好一切,这样子我们就可以不用自己去添加ListView去实现了,所以也不用实现onCreateView()方法。

实现ListFragment也比较简单,和listView的差不多:

首先准备数据mTitles ;
通过setListAdapter()设置简单的适配器;
最后实现onListItemClick()方法。

搞定!!!

PS:Fragment一些其他直接的子类,也是比较常用的:

DialogFragment
显示浮动对话框。使用此类创建对话框可有效地替代使用 Activity 类中的对话框帮助程序方法,因为您可以将Fragment对话框纳入由 Activity 管理的Fragment返回栈,从而使用户能够返回清除的Fragment。

ListFragment
显示由适配器(如 SimpleCursorAdapter)管理的一系列项目,类似于 ListActivity。它提供了几种管理列表视图的方法,如用于处理点击事件的 onListItemClick() 回调。

PreferenceFragment
以列表形式显示 Preference 对象的层次结构,类似于 PreferenceActivity。这在为您的应用创建“设置” Activity 时很有用处。


其次:

这里添加一个回调的方法,这是Fragment之间通信的一种方式。其实,对于跳转到DetailFragment的逻辑处理,也可以直接在TitleFragment处理(官方的例子是这样子的),通过getActivity()的方式获取宿主Activity的实例。但Fragment与Fragment的通信是需要借助Activity,所以我个人,还是比较喜欢直接用回调的方法,将逻辑处理放在Activity。


为确保宿主 Activity 实现此接口,在 onAttach() 回调方法(系统在向 Activity 添加Fragment时调用的方法)会通过转换传递到 onAttach() 中的 Activity 来实例化 Callbacks 的实例。如果 Activity 未实现接口,则片段会引发 ClassCastException。

对于接口的实现也可以通过在Activity中实现setCallbacks(Callbacks callbacks)方法,将当前的Activity传给参数。


该回调接口将会在MainActivity中实现,当用户点击TitleFragment列表项时,系统都会调用TitleFragment中的 onListItemClick(),然后该方法会调用 onItemClick() 与 Activity 共享事件。

到此,TitleFragment的解析就差不多了。


接下看看DetailFragment的实现,代码如下:

  1. public class DetailsFragment extends Fragment
  2. {
  3. String[] mDetails = { "IPTV", "VOD", "YOUTUBE", "DVB", "KTV" };
  4. /**
  5. * 相当于构造器
  6. * 传入需要的参数,设置给arguments
  7. */
  8. public static DetailsFragment newInstance(int index)
  9. {
  10. DetailsFragment f = new DetailsFragment();
  11. Bundle args = new Bundle();
  12. args.putInt("index", index);
  13. f.setArguments(args);
  14. return f;
  15. }
  16. @Override
  17. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
  18. {
  19. View view = inflater.inflate(R.layout.fragment_detail, container, false);
  20. TextView text = (TextView) view.findViewById(R.id.textView);
  21. text.setText(mDetails[getShownIndex()]);
  22. return view;
  23. }
  24. /**
  25. * 获取当前item的位置
  26. */
  27. public int getShownIndex()
  28. {
  29. return getArguments().getInt("index", 0);
  30. }
  31. }


主要是获取传入的参数,然后通过Bundle数据包将参数传给给Fragment,这样的好处,就是该Fragment就可以完全复用了。

在onCreateView()方法中,获取数据动态的设置布局,这里的布局比较简单,只有一个TextView,对应的fragment_detail.xml文件如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <TextView
  6. android:id="@+id/textView"
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. android:layout_centerHorizontal="true"
  10. android:layout_centerVertical="true"
  11. android:textSize="40sp"
  12. android:textColor="@android:color/holo_blue_light"
  13. android:text="TextView"/>
  14. </RelativeLayout>


现在我们回到MainActivity,实现我们的单屏模式,和双屏模式的切换,代码如下:

  1. public class MainActivity extends Activity implements TitleFragment.Callbacks
  2. {
  3.     /**
  4.      * 是否支持双屏模式,即标题和详情在两边同时显示出来,默认是不支持
  5.      */
  6.     private boolean mTwoPane = false;
  7.     /**
  8.      * 当前标题的索引值
  9.      */
  10.     private int mCurCheckPosition = 0;
  11.     @Override
  12.     protected void onCreate(Bundle savedInstanceState)
  13.     {
  14.         super.onCreate(savedInstanceState);
  15.         setContentView(R.layout.activity_main);
  16.         // 屏幕的旋转,可以保存状态,通过状态的保存,获取数据
  17.         if (savedInstanceState != null)
  18.         {
  19.             mCurCheckPosition = savedInstanceState.getInt("index", 0);
  20.         }
  21.         //判断是否双屏界面:获取“详情”的布局控件是否显示
  22.         View detailsFrame = findViewById(R.id.details);
  23.         mTwoPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
  24.         //也可以通过该方式,让Activity实现回调
  25. //        ((TitleFragment)getFragmentManager().findFragmentById(R.id.titles)).setCallbacks(this);
  26.         if (mTwoPane){
  27.             showDetails(mCurCheckPosition);
  28.         }
  29.     }
  30.     /**
  31.      * 实现Callbacks的回调方法,显示Detail页面的内容
  32.      * @param index
  33.      */
  34.     @Override
  35.     public void onItemClick(int index) {
  36.         showDetails(index);
  37.     }
  38.     /**
  39.      * 显示“详情”布局
  40.      * @param index 对应标题的索引
  41.      */
  42.     void showDetails(int index)
  43.     {
  44.         mCurCheckPosition = index;
  45.         //如果是双屏模式
  46.         if (mTwoPane){
  47.             /**
  48.              * 通过FragmentManager()获取DetailsFragment布局
  49.              */
  50.             DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.details);
  51.             if (details == null || details.getShownIndex() != index)
  52.             {
  53.                 /**
  54.                  * 传入不同的index,来创建不同的Fragment
  55.                  * 这样子可以减少Fragment的创建;
  56.                  * 不过,前提Fragment的布局功能大体相同
  57.                  */
  58.                 details = DetailsFragment.newInstance(index);
  59.                 //通过事务提交
  60.                 FragmentTransaction ft = getFragmentManager().beginTransaction();
  61.                 ft.replace(R.id.details, details);
  62.                 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
  63.                 ft.commit();
  64.             }
  65.         }else //不是双屏模式,则跳到另一个DetailsActivity,实现单屏模式
  66.         {
  67.             Intent intent = new Intent();
  68.             intent.setClass(MainActivity.this, DetailsActivity.class);
  69.             intent.putExtra("index", index);
  70.             startActivity(intent);
  71.         }
  72.     }
  1.     /**
  2.      * 保存位置状态,不然每次detail的内容多是第一个
  3.      * @param outState
  4.      */
  5.     @Override
  6.     protected void onSaveInstanceState(Bundle outState)
  7.     {
  8.         super.onSaveInstanceState(outState);
  9.         outState.putInt("index", mCurCheckPosition);
  10.     }
  1. }

代码上大部分以添加了注释,应该明白大体的流程,这里简单梳理一下:

首先系统会自动检测当前屏幕是横屏还是竖屏,然后会显示相应的main_activity.xml布局文件,那么就开始判断是否添加了details这个元素;

如果添加了,那么当前就是横屏,属于双屏模式,那么就在details元素的布局中添加DetailsFragment布局;

如果没有添加,那么当前就是竖屏,属于单屏模式,那么就直接跳到DetailActivity。


其中,detail需要判null,主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。



那接下来看看DetailActivity的实现:

  1. /**
  2. * 单屏模式下
  3. */
  4. public class DetailsActivity extends Activity
  5. {
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_details);
  10. /**
  11. * 屏幕旋转会重新创建该Activity
  12. * 如果屏幕从单屏旋转到双屏模式下,则销毁当前Activity,进入双屏模式
  13. */
  14. if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
  15. finish();
  16. return;
  17. }
  18. /**
  19. * 向当前的Activity的动态实现DetailsFragment
  20. */
  21. if (savedInstanceState == null)
  22. {
  23. DetailsFragment details = DetailsFragment.newInstance(getIntent().getIntExtra("index", 0));
  24. getFragmentManager().beginTransaction().add(R.id.layout_details, details).commit();
  25. }
  26. }
  27. }

如果是单屏模式,那么就在R.layout.activity_details添加DetailsFragment布局。

这里添加一个横竖屏的判断,当屏幕旋转时,会重新执行onCreate()方法,如果需要旋转到横屏,那么就finish掉当前的Activity。


好了,代码的详细的分析就就到此结束了,现在我们来看看最后的实现效果:

竖屏时:

                                                                                    

旋转屏幕时:

                                                         



代码写到这了,我们还可以直接适配Android pad(不考虑pad的横竖屏,只考虑屏幕的大小),其实和前面的横竖屏的适配几乎一样,只要添加一个布局目录就可以直接实现了。

在res目录新建一个layout-large目录,然后将layout-land目录下的布局直接复制在该目录下,就可以了,效果图如下,横竖多是一样的布局:

                                                


好了,栗子已经啃完了,其实比较简单,实际开发中,功能也会比较复杂,对于横竖屏切换的细节还要多考虑,比如一些布局需要判断是否为null等问题。


如有异议,欢迎指出,谢谢。


源码下载


声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号