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

前言

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>

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

在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;
	}
}

代码很简单,简单说说,顺便补充一下知识点,主要有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的实现,代码如下:

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);
    }

}


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

在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>


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

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等问题。


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


源码下载


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值