赞
踩
Fragment的应用越来越多,除了一些常用的:标签切换,引导页,广告位等,目前我们项目中,现在一些大大小小的自定义的控件,也先封装在Fragment中,然后在通过Activity来显示和隐藏,这样也切切实实的到达Fragment复用的效果,Activity的代码也少了。比如一些dialog、列表框、自定义的音量条等。这篇主要是对Fragment的应用,如果对Fragment的基础还不是很熟悉的话,可以移步到这里,有详细的介绍。
这篇也是对上一篇基础的加强,本篇介绍一个屏幕设配的栗子来解析,代码在最后也会给出。主要是在开发TV时,经常会有手机和电视端共用apk,那么就需要对屏幕的大小需要设配了。
该实例同时也是借鉴官方的例子,实现了屏幕横竖的切换适配,在最后再添加了Android pad大屏幕的支持。
首先,一般第一步就是新建一个MainActivity,用于控制布局的显示。其布局文件activity_main.xml如下:
- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <fragment
- android:id="@+id/titles"
- android:name="com.gotechcn.fragmentdemo.TitleFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- </FrameLayout>
在res目录下新建layout-land目录,然后这个目录下创建新的activity_main.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="horizontal">
-
- <fragment
- android:id="@+id/titles"
- android:name="com.gotechcn.fragmentdemo.TitleFragment"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="1"/>
-
- <FrameLayout
- android:id="@+id/details"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="2"
- android:background="?android:attr/detailsElementBackground"/>
-
- </LinearLayout>

这个布局和前面的布局,多了一个FrameLayout,这个布局是用来显示Detail的内容。当屏幕纵向显示时,系统会应用该布局。
现在,开始先来实现第一个Fragment:TitleFragment,具体的代码如下:
/**
- * 用于显示Title的布局
- *
- * 直接继承ListFragment
- * 可以少些一些代码,也可以继承Fragment,但就需要自己写ListView + 适配器了;
- * 继承了ListView,则可以不重写onCreateView()方法;
- */
- public class TitleFragment extends ListFragment
- {
- /**
- * 初始化化数据
- */
- private String [] mTitles = {"IPTV", "VOD", "YOUTUBE", "DVB", "KTV"};
-
- private Callbacks mCallbacks = null;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try {
- mCallbacks = (Callbacks) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement Callbacks");
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState)
- {
- super.onActivityCreated(savedInstanceState);
-
- //添加设配器,默认的布局,也可以自定义布局显示
- setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_activated_1, mTitles));
-
- //设置为单选模式
- getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
- }
-
- @Override
- public void onListItemClick(ListView l, View v, int position, long id)
- {
- super.onListItemClick(l, v, position, id);
- mCallbacks.onItemClick(position);
- }
-
-
- /**
- * item点击回调,在MainActivity实现该接口
- */
- public interface Callbacks
- {
- void onItemClick(int index);
- }
-
- public void setCallbacks(Callbacks callbacks){
- mCallbacks = callbacks;
- }
- }

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的实现,代码如下:
- public class DetailsFragment extends Fragment
- {
-
- String[] mDetails = { "IPTV", "VOD", "YOUTUBE", "DVB", "KTV" };
-
- /**
- * 相当于构造器
- * 传入需要的参数,设置给arguments
- */
- public static DetailsFragment newInstance(int index)
- {
- DetailsFragment f = new DetailsFragment();
- Bundle args = new Bundle();
- args.putInt("index", index);
- f.setArguments(args);
- return f;
- }
-
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
- {
- View view = inflater.inflate(R.layout.fragment_detail, container, false);
- TextView text = (TextView) view.findViewById(R.id.textView);
- text.setText(mDetails[getShownIndex()]);
- return view;
- }
-
- /**
- * 获取当前item的位置
- */
- public int getShownIndex()
- {
- return getArguments().getInt("index", 0);
- }
-
- }

在onCreateView()方法中,获取数据动态的设置布局,这里的布局比较简单,只有一个TextView,对应的fragment_detail.xml文件如下:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/textView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:textSize="40sp"
- android:textColor="@android:color/holo_blue_light"
- android:text="TextView"/>
- </RelativeLayout>
- public class MainActivity extends Activity implements TitleFragment.Callbacks
- {
-
- /**
- * 是否支持双屏模式,即标题和详情在两边同时显示出来,默认是不支持
- */
- private boolean mTwoPane = false;
- /**
- * 当前标题的索引值
- */
- private int mCurCheckPosition = 0;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
-
- // 屏幕的旋转,可以保存状态,通过状态的保存,获取数据
- if (savedInstanceState != null)
- {
- mCurCheckPosition = savedInstanceState.getInt("index", 0);
- }
- //判断是否双屏界面:获取“详情”的布局控件是否显示
- View detailsFrame = findViewById(R.id.details);
- mTwoPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
-
-
- //也可以通过该方式,让Activity实现回调
- // ((TitleFragment)getFragmentManager().findFragmentById(R.id.titles)).setCallbacks(this);
-
-
- if (mTwoPane){
- showDetails(mCurCheckPosition);
- }
- }
-
-
- /**
- * 实现Callbacks的回调方法,显示Detail页面的内容
- * @param index
- */
- @Override
- public void onItemClick(int index) {
- showDetails(index);
- }
-
-
- /**
- * 显示“详情”布局
- * @param index 对应标题的索引
- */
- void showDetails(int index)
- {
- mCurCheckPosition = index;
-
-
- //如果是双屏模式
- if (mTwoPane){
- /**
- * 通过FragmentManager()获取DetailsFragment布局
- */
- DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.details);
-
-
- if (details == null || details.getShownIndex() != index)
- {
- /**
- * 传入不同的index,来创建不同的Fragment
- * 这样子可以减少Fragment的创建;
- * 不过,前提Fragment的布局功能大体相同
- */
- details = DetailsFragment.newInstance(index);
-
-
- //通过事务提交
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(R.id.details, details);
- ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- ft.commit();
- }
-
-
- }else //不是双屏模式,则跳到另一个DetailsActivity,实现单屏模式
- {
- Intent intent = new Intent();
- intent.setClass(MainActivity.this, DetailsActivity.class);
- intent.putExtra("index", index);
- startActivity(intent);
- }
- }

- /**
- * 保存位置状态,不然每次detail的内容多是第一个
- * @param outState
- */
- @Override
- protected void onSaveInstanceState(Bundle outState)
- {
- super.onSaveInstanceState(outState);
- outState.putInt("index", mCurCheckPosition);
- }
-
- }
首先系统会自动检测当前屏幕是横屏还是竖屏,然后会显示相应的main_activity.xml布局文件,那么就开始判断是否添加了details这个元素;
如果添加了,那么当前就是横屏,属于双屏模式,那么就在details元素的布局中添加DetailsFragment布局;
如果没有添加,那么当前就是竖屏,属于单屏模式,那么就直接跳到DetailActivity。
其中,detail需要判null,主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。
那接下来看看DetailActivity的实现:
- /**
- * 单屏模式下
- */
- public class DetailsActivity extends Activity
- {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_details);
- /**
- * 屏幕旋转会重新创建该Activity
- * 如果屏幕从单屏旋转到双屏模式下,则销毁当前Activity,进入双屏模式
- */
- if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
- finish();
- return;
- }
-
- /**
- * 向当前的Activity的动态实现DetailsFragment
- */
- if (savedInstanceState == null)
- {
- DetailsFragment details = DetailsFragment.newInstance(getIntent().getIntExtra("index", 0));
- getFragmentManager().beginTransaction().add(R.id.layout_details, details).commit();
- }
- }
- }

如果是单屏模式,那么就在R.layout.activity_details添加DetailsFragment布局。
这里添加一个横竖屏的判断,当屏幕旋转时,会重新执行onCreate()方法,如果需要旋转到横屏,那么就finish掉当前的Activity。
好了,代码的详细的分析就就到此结束了,现在我们来看看最后的实现效果:
竖屏时:
旋转屏幕时:
代码写到这了,我们还可以直接适配Android pad(不考虑pad的横竖屏,只考虑屏幕的大小),其实和前面的横竖屏的适配几乎一样,只要添加一个布局目录就可以直接实现了。
在res目录新建一个layout-large目录,然后将layout-land目录下的布局直接复制在该目录下,就可以了,效果图如下,横竖多是一样的布局:
好了,栗子已经啃完了,其实比较简单,实际开发中,功能也会比较复杂,对于横竖屏切换的细节还要多考虑,比如一些布局需要判断是否为null等问题。
如有异议,欢迎指出,谢谢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。