android开发中如何自定义UI模板【图解教程】

 更新时间:2016年9月20日 19:55  点击:1978
android开发自定义UI模板应用非常广范,因为我们得根据自己的设计风格,本文我们将通过图文教程讲解android UI自定义模板。

每个设计良好的App都是自定义标题栏,在自定义标题栏的过程中大部分人可能都是自定义一个标题的xml文件,然后在需要的地方直接通过include来引用,这比起在每个布局文件中写标题栏已经进化很多了,但仍然不是最简单有效的方法,我们为什么不能自定义一个标题控件呢?今天就带大家自己做一个标题栏控件。效果图如下:

这里写图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ToolBar">
        <attr name="title" format="string" />
        <attr name="titleTextSize" format="dimension" />
        <attr name="titleTextColor" format="color" />
        <attr name="leftBackground" format="reference|color" />
        <attr name="leftText" format="string" />
        <attr name="leftTextColor" format="reference|color" />
        <attr name="rightBackground" format="reference|color" />
        <attr name="rightText" format="string" />
        <attr name="rightTextColor" format="reference|color" />
    </declare-styleable>
</resources>


前面的name是我们要使用的属性名称,后面的format表示该属性接受的值的格式,string表示该属性的值是一个字符串,color表示该属性的值是一个颜色值,dimension表示该属性的值是一个尺寸,reference表示该属性的值可以参考某一个资源id,其他常见的format值还有:boolean(布尔值)、float(浮点值)、integer(整型值)、fraction(百分数)、enum(枚举值)、flag(位或运算)。

第二步:自定义标题类
在Java文件中自定义一个类继承RelativeLayout,并实现它的构造方法,我们的标题栏由三部分组成,左右两边各是一个Button,中间是一个TextView,因此我们在这个布局文件中要做的事就是对这三个控件进行处理。

先声明标题栏的三个空间及相关参数,这些参数都是根据atts.xml中来设置的,因为我们在引用自定义控件的时候是从xml中引用的,属性的设置都在xml文件中,我们从xml文件中拿到属性的值后再对控件设置赋值。

    /**
     * 标题栏的三个控件
     */
    private Button leftBtn, rightBtn;
    private TextView title;
    /**
     * 左边按钮的属性
     */
    private int leftTextColor;
    private Drawable leftBackground;
    private String leftText;
    /**
     * 右边按钮的属性
     */
    private int rightTextColor;
    private Drawable rightBackground;
    private String rightText;
    /**
     * 中间TextView的属性
     */
    private int titleTextColor;
    private String titleText;
    private float titleTextSize;
    /**
     * 三个控件的布局参数
     */
    private LayoutParams leftParams, rightParams, titleParams;


下面是构造方法,构造方法传入两个参数,一个是上下文参数,另外一个是AttributeSet,AttributeSet是一个属性的集合,用它可以处理一组xml标签集合。使用ta.getXXX方法,我们可以先从xml文件获得属性的值,然后把这些值设置给控件。最后通过LayoutParams来设置控件的宽高,设置好宽高之后,调用addView方法,添加控件。

    public MyToolBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs,
                R.styleable.ToolBar);
        leftTextColor = ta.getColor(R.styleable.ToolBar_leftTextColor, 0);
        leftBackground = ta.getDrawable(R.styleable.ToolBar_leftBackground);
        leftText = ta.getString(R.styleable.ToolBar_leftText);
        rightTextColor = ta.getColor(R.styleable.ToolBar_rightTextColor, 0);
        rightBackground = ta.getDrawable(R.styleable.ToolBar_rightBackground);
        rightText = ta.getString(R.styleable.ToolBar_rightText);
        titleText = ta.getString(R.styleable.ToolBar_title);
        titleTextColor = ta.getColor(R.styleable.ToolBar_titleTextColor, 0);
        titleTextSize = ta.getDimension(R.styleable.ToolBar_titleTextSize, 0);
        //对ta进行回收
        ta.recycle();
        leftBtn = new Button(context);
        rightBtn = new Button(context);
        title = new TextView(context);
        /**
         * 设置属性
         */
        leftBtn.setText(leftText);
        leftBtn.setTextColor(leftTextColor);
        leftBtn.setBackground(leftBackground);
        rightBtn.setText(rightText);
        rightBtn.setTextColor(rightTextColor);
        rightBtn.setBackground(rightBackground);
        title.setText(titleText);
        title.setTextColor(titleTextColor);
        title.setTextSize(titleTextSize);
        title.setGravity(Gravity.CENTER);
        //设置整体背景颜色
        setBackgroundColor(0x7CFC0055);
        leftParams = new LayoutParams(
                android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
                android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
        leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
        //添加控件
        addView(leftBtn, leftParams);
        rightParams = new LayoutParams(
                android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
                android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
        rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
        addView(rightBtn, rightParams);
        titleParams = new LayoutParams(
                android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
                android.view.ViewGroup.LayoutParams.MATCH_PARENT);
        titleParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
        addView(title, titleParams);
    }


第三步:引用我们定义的控件
自定义好控件之后,我们就可以使用自定义的控件了,在主布局的xml文件中引用我们自定义的控件。自定义控件的前三个属性都是以android:开头,这表示这些属性都是系统的,后面的属性以custombar开头,表示这些属性都是我们自定义的,为了能够使用自定义的custombar,我们需要在RelativeLayout中添加一句:

xmlns:custombar="http://schemas.android.com/apk/res/com.example.mytoolbar"

注意后面的com.example.mytoolbar是你应用的包名称。如果阁下使用的不是eclipse而是android studio,那么这一行不用这么麻烦,只需要写上:

xmlns:custombar="http://schemas.android.com/apk/res-auto"

我们自定义的属性就是我们在atts.xml中声明的要设置的属性。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custombar="http://schemas.android.com/apk/res/com.example.mytoolbar"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <com.example.mytoolbar.MyToolBar
        android:id="@+id/mytoolbar"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        custombar:leftBackground="@android:color/holo_blue_light"
        custombar:leftText="返回"
        custombar:leftTextColor="@android:color/black"
        custombar:rightBackground="@android:color/holo_blue_light"
        custombar:rightText="更多"
        custombar:rightTextColor="@android:color/black"
        custombar:title="标题栏"
        custombar:titleTextColor="@android:color/black"
        custombar:titleTextSize="18sp" >
    </com.example.mytoolbar.MyToolBar>
</RelativeLayout>


做完这些工作之后,运行你的项目,就能看到我们在文章开头给出的那个画面了。

第四步:为自定义控件添加事件

好像还少点什么,是的,我们的控件都还没有点击事件。要给事件设置点击事件,需要先在自定义控件中声明一个事件接口,并声明一个接口的实例:

private OnToolBarClickListener listener;
    public interface OnToolBarClickListener {
        /**
         * 左边按钮点击事件
         */
        public void leftClick();
        /**
         * 右边按钮点击事件
         */
        public void rightClick();
    }


然后暴露出来一个方法给其他类调用,这个方法的参数就是这个接口:

    public void setOnToolBarClickListener(OnToolBarClickListener listener) {
        this.listener = listener;
    }

最后在左右两个按钮的点击事件中调用接口中的方法即可,聪明的看官猜猜这是什么模式?

  

      leftBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.leftClick();
            }
        });
        rightBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.rightClick();
            }
        });


方法写好了,我们在MainActivity中调用看看:

public class MainActivity extends Activity {
    private MyToolBar toolBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getActionBar().hide();
        this.toolBar = (MyToolBar) this.findViewById(R.id.mytoolbar);
        toolBar.setOnToolBarClickListener(new OnToolBarClickListener() {
            
            @Override
            public void rightClick() {
                Toast.makeText(MainActivity.this,"右边点击", Toast.LENGTH_LONG).show();
            }
            
            @Override
            public void leftClick() {
                Toast.makeText(MainActivity.this,"左边点击", Toast.LENGTH_LONG).show();
            }
        });
    }
}

这里写图片描述


Dashboard Android用户自定义UI设计模板


Dashboard,一种专门针对入口界面设计的应用程序,Dashboard (为仪表板之意)原来是苹果公司 Mac OS X v10.4 Tiger 作业系统中的应用程序,用作widget的小型应用程式之执行基础。

本文我们将简要介绍Android的用户自定义UI设计模板Dashboard,它借助清晰且尺寸巨大的图标来标示出主要功能及可选区域,用以为用户提供适用的最新信息。


Android用户自定义UI设计模板-Dashboard

我们回顾Goole I/O 2010年大会,其推介的Android用户界面设计模板(Android UI design patterns)可以提供相关功能以简化用户的操作界面。这体现的正是Dashboard的特色。


选项卡模式

问题

在移动类产品的实际使用中,借助清晰快速的导航来实现主要功能显得极为重要。它们应该便捷有效,帮助用户迅速实现某些基本的操作(例如在社交网站上发布动态,发送消息或是拍照等)。


解决方案

解决方案

应用程序的入口界面应当具备清爽的视觉体验及易于访问的特性(尤其是针对常用的应用程序)。

更多实例

facebook


miso


picplz


twitter

结论

◆简便迅捷地实现主要功能

◆清晰友好的入口界面

◆便于用户理解及掌握

◆不失时机地向用户展示品牌形象

◆所提供的选项应提示当前应用程序的基本信息或作用范围


Fragment中文解释是碎片的意思,主要用在大屏幕设备上,例如平板电脑上,支持更加动态和灵活的UI设计。Fragment在你的应用中相当于是一个模块化和可重用的组件,因为Fragment定义了它自己的布局,以及通过使用它自己的生命周期回调方法定义了它自己的行为,你可以将Fragment包含到多个Activity中。

本篇为大家说明Fragment如何产生,什么是Fragment,Fragment生命周期,如何静态和动态的使用Fragment,Fragment回退栈,Fragment事务;以及Fragment的一些特殊用途,例如:没有布局的Fragment有何用处?Fragment如何与Activity交互?Fragment如何创建对话框?Fragment如何与ActionBar集成等等。


1、Fragment的产生与介绍

Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。


2、Fragment的生命周期

Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系:

01.png



可以看到Fragment比Activity多了几个额外的生命周期回调方法:
onAttach(Activity)
当Fragment与Activity发生关联时调用。
onCreateView(LayoutInflater, ViewGroup,Bundle)
创建该Fragment的视图
onActivityCreated(Bundle)
当Activity的onCreate方法返回时调用
onDestoryView()
与onCreateView想对应,当该Fragment的视图被移除时调用
onDetach()
与onAttach相对应,当Fragment与Activity关联被取消时调用
注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,


3、静态的使用Fragment

嘿嘿,终于到使用的时刻了~~

这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中。步骤:

1、继承Fragment,重写onCreateView决定Fragemnt的布局

2、在Activity中声明此Fragment,就当和普通的View一样

下面展示一个例子(我使用2个Fragment作为Activity的布局,一个Fragment用于标题布局,一个Fragment用于内容布局):

TitleFragment的布局文件:

<?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="45dp"  
    android:background="@drawable/title_bar" >  
  
    <ImageButton  
        android:id="@+id/id_title_left_btn"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_centerVertical="true"  
        android:layout_marginLeft="3dp"  
        android:background="@drawable/showleft_selector" />  
  
    <TextView  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:gravity="center"  
        android:text="我不是微信"  
        android:textColor="#fff"  
        android:textSize="20sp"  
        android:textStyle="bold" />  
  
</RelativeLayout>



TitleFragment

package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.ViewGroup;  
import android.widget.ImageButton;  
import android.widget.Toast;  
  
public class TitleFragment extends Fragment  
{  
  
    private ImageButton mLeftMenu;  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_title, container, false);  
        mLeftMenu = (ImageButton) view.findViewById(R.id.id_title_left_btn);  
        mLeftMenu.setOnClickListener(new OnClickListener()  
        {  
            @Override  
            public void onClick(View v)  
            {  
                Toast.makeText(getActivity(),  
                        "i am an ImageButton in TitleFragment ! ",  
                        Toast.LENGTH_SHORT).show();  
            }  
        });  
        return view;  
    }  
}



同理还有ContentFragment的其布局文件:


<?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" >  
  
    <TextView  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:gravity="center"  
        android:text="使用Fragment做主面板"  
        android:textSize="20sp"  
        android:textStyle="bold" />  
  
</LinearLayout>
package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
  
public class ContentFragment extends Fragment  
{  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        return inflater.inflate(R.layout.fragment_content, container, false);  
    }  
  
}



MainActivity

package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.Window;  
  
public class MainActivity extends Activity  
{  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
    }  
  
}



Activity的布局文件:

<RelativeLayout 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" >  
  
    <fragment  
        android:id="@+id/id_fragment_title"  
        android:name="com.zhy.zhy_fragments.TitleFragment"  
        android:layout_width="fill_parent"  
        android:layout_height="45dp" />  
  
    <fragment  
        android:layout_below="@id/id_fragment_title"  
        android:id="@+id/id_fragment_content"  
        android:name="com.zhy.zhy_fragments.ContentFragment"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent" />  
  
</RelativeLayout>



是不是把Fragment当成普通的View一样声明在Activity的布局文件中,然后所有控件的事件处理等代码都由各自的Fragment去处理,瞬间觉得Activity好干净有木有~~代码的可读性、复用性以及可维护性是不是瞬间提升了~~~下面看下效果图:

01.gif



4、动态的使用Fragment

上面已经演示了,最简单的使用Fragment的方式~下面介绍如何动态的添加、更新、以及删除Fragment

为了动态使用Fragment,我们修改一下Actvity的布局文件,中间使用一个FrameLayout,下面添加四个按钮~~~嘿嘿~~不是微信的按钮- -!

<RelativeLayout 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" >  
  
    <fragment  
        android:id="@+id/id_fragment_title"  
        android:name="com.zhy.zhy_fragments.TitleFragment"  
        android:layout_width="fill_parent"  
        android:layout_height="45dp" />  
  
    <include  
        android:id="@+id/id_ly_bottombar"  
        android:layout_width="fill_parent"  
        android:layout_height="55dp"  
        android:layout_alignParentBottom="true"  
        layout="@layout/bottombar" />  
  
    <FrameLayout  
        android:id="@+id/id_content"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:layout_above="@id/id_ly_bottombar"  
        android:layout_below="@id/id_fragment_title" />  
  
</RelativeLayout>



底部四个按钮的布局就不贴了,到时看效果图就明白了~~

下面主Activity

package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.Window;  
import android.widget.LinearLayout;  
  
public class MainActivity extends Activity implements OnClickListener  
{  
    private LinearLayout mTabWeixin;  
    private LinearLayout mTabFriend;  
  
    private ContentFragment mWeixin;  
    private FriendFragment mFriend;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        // 初始化控件和声明事件  
        mTabWeixin = (LinearLayout) findViewById(R.id.tab_bottom_weixin);  
        mTabFriend = (LinearLayout) findViewById(R.id.tab_bottom_friend);  
        mTabWeixin.setOnClickListener(this);  
        mTabFriend.setOnClickListener(this);  
  
        // 设置默认的Fragment  
        setDefaultFragment();  
    }  
  
    private void setDefaultFragment()  
    {  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction transaction = fm.beginTransaction();  
        mWeixin = new ContentFragment();  
        transaction.replace(R.id.id_content, mWeixin);  
        transaction.commit();  
    }  
  
    @Override  
    public void onClick(View v)  
    {  
        FragmentManager fm = getFragmentManager();  
        // 开启Fragment事务  
        FragmentTransaction transaction = fm.beginTransaction();  
  
        switch (v.getId())  
        {  
        case R.id.tab_bottom_weixin:  
            if (mWeixin == null)  
            {  
                mWeixin = new ContentFragment();  
            }  
            // 使用当前Fragment的布局替代id_content的控件  
            transaction.replace(R.id.id_content, mWeixin);  
            break;  
        case R.id.tab_bottom_friend:  
            if (mFriend == null)  
            {  
                mFriend = new FriendFragment();  
            }  
            transaction.replace(R.id.id_content, mFriend);  
            break;  
        }  
        // transaction.addToBackStack();  
        // 事务提交  
        transaction.commit();  
    }  
  
}



可以看到我们使用FragmentManager对Fragment进行了动态的加载,这里使用的是replace方法~~下一节我会详细介绍FragmentManager的常用API。

注:如果使用Android3.0以下的版本,需要引入v4的包,然后Activity继承FragmentActivity,然后通过getSupportFragmentManager获得FragmentManager。不过还是建议版Menifest文件的uses-sdk的minSdkVersion和targetSdkVersion都改为11以上,这样就不必引入v4包了。

代码中间还有两个Fragment的子类,ContentFragment上面已经见过,FriendFragment其实类似:


package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
  
public class FriendFragment extends Fragment  
{  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        return inflater.inflate(R.layout.fragment_friend, container, false);  
    }  
  
}



效果图:


01.gif



可以看到很好的实现了效果,其实这个效果以前的博客中也出现过,在博客:Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager,有兴趣可以看看。ps:为了代码的简洁,就不添加按钮的点击变化什么的了,主要讲解功能了~~~


5、Fragment家族常用的API

Fragment常用的三个类:

android.app.Fragment 主要用于定义Fragment

android.app.FragmentManager 主要用于在Activity中操作Fragment

android.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词,一定能明白~

a、获取FragmentManage的方式:

getFragmentManager() // v4中,getSupportFragmentManager

b、主要的操作都是FragmentTransaction的方法

FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务

transaction.add()

往Activity中添加一个Fragment

transaction.remove()

从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。

transaction.replace()

使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~

transaction.hide()

隐藏当前的Fragment,仅仅是设为不可见,并不会销毁

transaction.show()

显示之前隐藏的Fragment

detach()

会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。

attach()

重建view视图,附加到UI上并显示。

transatcion.commit()//提交一个事务

注意:常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。

上述,基本是操作Fragment的所有的方式了,在一个事务开启到提交可以进行多个的添加、移除、替换等操作。

值得注意的是:如果你喜欢使用Fragment,一定要清楚这些方法,哪个会销毁视图,哪个会销毁实例,哪个仅仅只是隐藏,这样才能更好的使用它们。

a、比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望会到A还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。

b、再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。

c、remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。

上述已经介绍完成了Fragment常用的一些方法,相信看完,大家一定清楚了Fragment的产生理由,以及如何使用Fragment,再根据API的讲解,也能明白,曾经为何觉得Fragment会出现一些列乱七八槽的问题,终究是因为没有弄清楚其生命周期。

由于篇幅原因,剩下的内容留到下一篇了。在下一篇,会介绍:

1、如何管理Fragment回退栈

2、Fragment如何与Activity交互

3、Fragment与Activity交互的最佳实践

4、没有视图的Fragment的用处

5、使用Fragment创建对话框

6、如何与ActionBar,MenuItem集成等~~




Android Fragment 完全解析继续


本篇将介绍上篇博客提到的:如何管理Fragment回退栈,Fragment如何与Activity交互,Fragment与Activity交互的最佳实践,没有视图的Fragment的用处,使用Fragment创建对话框,如何与ActionBar,MenuItem集成等~~


1、管理Fragment回退栈

类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。

看这样一个效果图:

01.gif


点击第一个按钮,切换到第二个界面,点击第二个按钮,切换到第三个界面,然后点击Back键依次回退。这像不像初学Android时的Activity跳转,当然了,这里肯定不是,不然我就跪了。这里是Fragment实现的,用户点击Back,实际是Fragment回退栈不断的弹栈。

如何添加一个Fragment事务到回退栈:

FragmentTransaction.addToBackStack(String)

下面讲解代码:很明显一共是3个Fragment和一个Activity.

先看Activity的布局文件:


<RelativeLayout 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" >  
  
    <FrameLayout  
        android:id="@+id/id_content"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent" >  
    </FrameLayout>  
  
</RelativeLayout>


不同的Fragment就在这个FrameLayout中显示。

MainActivity.java

package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.view.Window;  
  
public class MainActivity extends Activity  
{  
  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.add(R.id.id_content, new FragmentOne(),"ONE");  
        tx.commit();  
    }  
  
}


很简单,直接将FragmentOne添加到布局文件中的FrameLayout中,注意这里并没有调用FragmentTransaction.addToBackStack(String),因为我不喜欢在当前显示时,点击Back键出现白板。而是正确的相应Back键,即退出我们的Activity.

下面是FragmentOne


package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.ViewGroup;  
import android.widget.Button;  
  
public class FragmentOne extends Fragment implements OnClickListener  
{  
  
    private Button mBtn;  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_one, container, false);  
        mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);  
        mBtn.setOnClickListener(this);  
        return view;  
    }  
  
    @Override  
    public void onClick(View v)  
    {  
        FragmentTwo fTwo = new FragmentTwo();  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.replace(R.id.id_content, fTwo, "TWO");  
        tx.addToBackStack(null);  
        tx.commit();  
  
    }  
  
}



我们在点击FragmentOne中的按钮时,使用了replace方法,如果你看了前一篇博客,一定记得replace是remove和add的合体,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。这里很明显,我们调用tx.addToBackStack(null);将当前的事务添加到了回退栈,所以FragmentOne实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView,证据就是:仔细看上面的效果图,我们在跳转前在文本框输入的内容,在用户Back得到第一个界面的时候不见了。

接下来FragmentTwo


package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.ViewGroup;  
import android.widget.Button;  
  
public class FragmentTwo extends Fragment implements OnClickListener  
{  
  
    private Button mBtn ;  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_two, container, false);  
        mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);  
        mBtn.setOnClickListener(this);  
        return view ;   
    }  
    @Override  
    public void onClick(View v)  
    {  
        FragmentThree fThree = new FragmentThree();  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.hide(this);  
        tx.add(R.id.id_content , fThree, "THREE");  
//      tx.replace(R.id.id_content, fThree, "THREE");  
        tx.addToBackStack(null);  
        tx.commit();  
    }  
  
  
}



这里点击时,我们没有使用replace,而是先隐藏了当前的Fragment,然后添加了FragmentThree的实例,最后将事务添加到回退栈。这样做的目的是为了给大家提供一种方案:如果不希望视图重绘该怎么做,请再次仔细看效果图,我们在FragmentTwo的EditText填写的内容,用户Back回来时,数据还在~~~

最后FragmentThree就是简单的Toast了:


package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.ViewGroup;  
import android.widget.Button;  
import android.widget.Toast;  
  
public class FragmentThree extends Fragment implements OnClickListener  
{  
  
    private Button mBtn;  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_three, container, false);  
        mBtn = (Button) view.findViewById(R.id.id_fragment_three_btn);  
        mBtn.setOnClickListener(this);  
        return view;  
    }  
  
    @Override  
    public void onClick(View v)  
    {  
        Toast.makeText(getActivity(), " i am a btn in Fragment three",  
                Toast.LENGTH_SHORT).show();  
    }  
  
}



好了,经过上面的介绍,应该已经知道Fragment回退栈是怎么一回事了,以及hide,replace等各自的应用的场景。

这里极其注意一点:上面的整体代码不具有任何参考价值,纯粹为了显示回退栈,在后面讲解了Fragment与Activity通信以后,会重构上面的代码!


2、Fragment与Activity通信

因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:

a、如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法

b、如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。

c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。

注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。


3、Fragment与Activity通信的最佳实践

因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。

下面我通过两种方式的代码,分别重构,FragmentOne和FragmentTwo的点击事件,以及Activity对点击事件的响应:

首先看FragmentOne


package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.ViewGroup;  
import android.widget.Button;  
  
public class FragmentOne extends Fragment implements OnClickListener  
{  
    private Button mBtn;  
  
    /** 
     * 设置按钮点击的回调 
     * @author zhy 
     * 
     */  
    public interface FOneBtnClickListener  
    {  
        void onFOneBtnClick();  
    }  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_one, container, false);  
        mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);  
        mBtn.setOnClickListener(this);  
        return view;  
    }  
  
    /** 
     * 交给宿主Activity处理,如果它希望处理 
     */  
    @Override  
    public void onClick(View v)  
    {  
        if (getActivity() instanceof FOneBtnClickListener)  
        {  
            ((FOneBtnClickListener) getActivity()).onFOneBtnClick();  
        }  
    }  
  
}



可以看到现在的FragmentOne不和任何Activity耦合,任何Activity都可以使用;并且我们声明了一个接口,来回调其点击事件,想要管理其点击事件的Activity实现此接口就即可。可以看到我们在onClick中首先判断了当前绑定的Activity是否实现了该接口,如果实现了则调用。

再看FragmentTwo


package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.view.ViewGroup;  
import android.widget.Button;  
  
public class FragmentTwo extends Fragment implements OnClickListener  
{  
  
      
    private Button mBtn ;  
      
    private FTwoBtnClickListener fTwoBtnClickListener ;  
      
    public interface FTwoBtnClickListener  
    {  
        void onFTwoBtnClick();  
    }  
    //设置回调接口  
    public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)  
    {  
        this.fTwoBtnClickListener = fTwoBtnClickListener;  
    }  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_two, container, false);  
        mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);  
        mBtn.setOnClickListener(this);  
        return view ;   
    }  
    @Override  
    public void onClick(View v)  
    {  
        if(fTwoBtnClickListener != null)  
        {  
            fTwoBtnClickListener.onFTwoBtnClick();  
        }  
    }  
  
}



与FragmentOne极其类似,但是我们提供了setListener这样的方法,意味着Activity不仅需要实现该接口,还必须显示调用mFTwo.setfTwoBtnClickListener(this)。

最后看Activity :

package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.view.Window;  
  
import com.zhy.zhy_fragments.FragmentOne.FOneBtnClickListener;  
import com.zhy.zhy_fragments.FragmentTwo.FTwoBtnClickListener;  
  
public class MainActivity extends Activity implements FOneBtnClickListener,  
        FTwoBtnClickListener  
{  
  
    private FragmentOne mFOne;  
    private FragmentTwo mFTwo;  
    private FragmentThree mFThree;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        mFOne = new FragmentOne();  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.add(R.id.id_content, mFOne, "ONE");  
        tx.commit();  
    }  
  
    /** 
     * FragmentOne 按钮点击时的回调 
     */  
    @Override  
    public void onFOneBtnClick()  
    {  
  
        if (mFTwo == null)  
        {  
            mFTwo = new FragmentTwo();  
            mFTwo.setfTwoBtnClickListener(this);  
        }  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.replace(R.id.id_content, mFTwo, "TWO");  
        tx.addToBackStack(null);  
        tx.commit();  
    }  
  
    /** 
     * FragmentTwo 按钮点击时的回调 
     */  
    @Override  
    public void onFTwoBtnClick()  
    {  
        if (mFThree == null)  
        {  
            mFThree = new FragmentThree();  
  
        }  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.hide(mFTwo);  
        tx.add(R.id.id_content, mFThree, "THREE");  
        // tx.replace(R.id.id_content, fThree, "THREE");  
        tx.addToBackStack(null);  
        tx.commit();  
    }  
  
}



代码重构结束,与开始的效果一模一样。上面两种通信方式都是值得推荐的,随便选择一种自己喜欢的。这里再提一下:虽然Fragment和Activity可以通过getActivity与findFragmentByTag或者findFragmentById,进行任何操作,甚至在Fragment里面操作另外的Fragment,但是没有特殊理由是绝对不提倡的。Activity担任的是Fragment间类似总线一样的角色,应当由它决定Fragment如何操作。另外虽然Fragment不能响应Intent打开,但是Activity可以,Activity可以接收Intent,然后根据参数判断显示哪个Fragment。

4、如何处理运行时配置发生变化

运行时配置发生变化,最常见的就是屏幕发生旋转,如果你不知道如何处理屏幕变化可以参考:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

这里提一下:很多人觉得强制设置屏幕的方向就可以了,但是有一点,当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。比如上例:如果你把上面的例子你至于FragmentThree界面,然后处于后台状态,长时间后你会发现当你再次通过home打开时,上面FragmentThree与FragmentOne叠加在一起,这就是因为你的Activity重新启动,在原来的FragmentThree上又绘制了一个FragmentOne。

好了,下面看一段代码:

Activity:

package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.view.Window;  
  
public class MainActivity extends Activity  
  
{  
    private FragmentOne mFOne;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        mFOne = new FragmentOne();  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.add(R.id.id_content, mFOne, "ONE");  
        tx.commit();  
  
    }  
  
}



Fragment

package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
  
public class FragmentOne extends Fragment  
{  
    private static final String TAG = "FragmentOne";  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        Log.e(TAG, "onCreateView");  
        View view = inflater.inflate(R.layout.fragment_one, container, false);  
        return view;  
    }  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        // TODO Auto-generated method stub  
        super.onCreate(savedInstanceState);  
  
        Log.e(TAG, "onCreate");  
    }  
  
    @Override  
    public void onDestroyView()  
    {  
        // TODO Auto-generated method stub  
        super.onDestroyView();  
        Log.e(TAG, "onDestroyView");  
    }  
  
    @Override  
    public void onDestroy()  
    {  
        // TODO Auto-generated method stub  
        super.onDestroy();  
        Log.e(TAG, "onDestroy");  
    }  
  
}


很简单的代码,当你运行之后,不断的旋转屏幕,你会发现每旋转一次屏幕,屏幕上就多了一个FragmentOne的实例,并且后台log会打印出许多套生命周期的回调。

类似:

07-20 08:18:46.651: E/FragmentOne(1633): onCreate  
07-20 08:18:46.651: E/FragmentOne(1633): onCreate  
07-20 08:18:46.651: E/FragmentOne(1633): onCreate  
07-20 08:18:46.681: E/FragmentOne(1633): onCreateView  
07-20 08:18:46.831: E/FragmentOne(1633): onCreateView  
07-20 08:18:46.891: E/FragmentOne(1633): onCreateView 

这是为什么呢,因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。

那么如何解决呢:

其实通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建:

默认的savedInstanceState会存储一些数据,包括Fragment的实例:通过打印可以看出:

07-20 08:23:12.952: E/FragmentOne(1782): Bundle[{android:fragments=android.app.FragmentManagerState@40d0b7b8, android:viewHierarchyState=Bundle[{android:focusedViewId=2131230721, android:views=android.util.SparseArray@40d0af68}]}]  

所以,我们简单改一下代码,只有在savedInstanceState==null时,才进行创建Fragment实例:


package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.Window;  
  
public class MainActivity extends Activity  
  
{  
    private static final String TAG = "FragmentOne";  
    private FragmentOne mFOne;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        Log.e(TAG, savedInstanceState+"");  
          
        if(savedInstanceState == null)  
        {  
            mFOne = new FragmentOne();  
            FragmentManager fm = getFragmentManager();  
            FragmentTransaction tx = fm.beginTransaction();  
            tx.add(R.id.id_content, mFOne, "ONE");  
            tx.commit();  
        }  
          
          
  
    }  
  
}


现在无论进行多次旋转都只会有一个Fragment实例在Activity中。

现在还存在一个问题,就是重新绘制时,Fragment发生重建,原本的数据如何保持?

其实和Activity类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。

由于篇幅原因,就不贴测试代码了。


5、Fragmeny与ActionBar和MenuItem集成

Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。

a、在Fragment的onCreate中调用 setHasOptionsMenu(true);

b、然后在Fragment子类中实现onCreateOptionsMenu

c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了Activity也可以直接处理该MenuItem的点击事件。

代码:

Fragment

package com.zhy.zhy_fragments;  
  
import android.app.Fragment;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.Menu;  
import android.view.MenuInflater;  
import android.view.MenuItem;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.Toast;  
  
public class FragmentOne extends Fragment  
{  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setHasOptionsMenu(true);  
    }  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_one, container, false);  
        return view;  
    }  
  
    @Override  
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)  
    {  
        inflater.inflate(R.menu.fragment_menu, menu);  
    }  
  
    @Override  
    public boolean onOptionsItemSelected(MenuItem item)  
    {  
        switch (item.getItemId())  
        {  
        case R.id.id_menu_fra_test:  
            Toast.makeText(getActivity(), "FragmentMenuItem1", Toast.LENGTH_SHORT).show();  
            break;  
        }  
        return true;  
    }  
  
}


Activity

package com.zhy.zhy_fragments;  
  
import android.app.Activity;  
import android.app.FragmentManager;  
import android.app.FragmentTransaction;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.Menu;  
import android.view.MenuItem;  
import android.view.Window;  
import android.widget.Toast;  
  
public class MainActivity extends Activity  
  
{  
    private static final String TAG = "FragmentOne";  
    private FragmentOne mFOne;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        Log.e(TAG, savedInstanceState + "");  
  
        if (savedInstanceState == null)  
        {  
            mFOne = new FragmentOne();  
            FragmentManager fm = getFragmentManager();  
            FragmentTransaction tx = fm.beginTransaction();  
            tx.add(R.id.id_content, mFOne, "ONE");  
            tx.commit();  
        }  
  
    }  
  
    @Override  
    public boolean onCreateOptionsMenu(Menu menu)  
    {  
        super.onCreateOptionsMenu(menu);  
        getMenuInflater().inflate(R.menu.main, menu);  
        return true;  
    }  
  
    @Override  
    public boolean onOptionsItemSelected(MenuItem item)  
    {  
        switch (item.getItemId())  
        {  
        case R.id.action_settings:  
            Toast.makeText(this, "setting", Toast.LENGTH_SHORT).show();  
            return true;  
        default:  
            //如果希望Fragment自己处理MenuItem点击事件,一定不要忘了调用super.xxx  
            return super.onOptionsItemSelected(item);  
        }  
    }  
  
}



效果图:

01.gif


好了,可以很好的看到,Fragment可以添加MenuItem,也可以自己处理点击~~~

6、没有布局的Fragment的作用

没有布局文件Fragment实际上是为了保存,当Activity重启时,保存大量数据准备的

Android搜索有Search Dialog及SearchView两种方式,本教程我们讲讲用Xamarin框架如果开发ndroid搜索框 Search Dialog。

Android 的搜索有两种可用方式:Search Dialog,SearchView。

SearchView 简单,随意使用,这里主要说说 Search Dialog 的基本用法, 因为 Xamarin 的处理方式稍稍和 原生 Android 有些不同。

效果:



Searchable

要使用 Search Dialog 需要配置一个搜索配置文件 : 放到Resources/xml 目录下 。

如果xml 目录不存在,需要手动创建一个。

文件名随便, 一般取 searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/SearchLabel"
    android:hint="@string/SearchPlaceHolder"
    >
</searchable>

根节点 必须是 searchable 。

android:label 是必填项

android:hint 就是 html5 中的 placeholder

其它属选项请参考:

http://developer.android.com/guide/topics/search/searchable-config.html

Activity 启用 Search Diaog 功能

要启用 Search Dialog , 还必须在 Activity 上开启搜索

在原生的 Android App 里可以直接修改 manifest 文件。

如下 :

<activity android:name="Search" >
  <intent-filter>
    <action android:name="android.intent.action.SEARCH" />
  </intent-filter>
  <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity>

<activity android:name=".MainActivity" >
  <meta-data android:name="android.app.default_searchable"
             android:value=".Search" />
</activity>

android:resource="@xml/searchable" 即指上面的配置文件 (xml/searchable.xml)

黄色标注出来的,为固定字符串。

这一部分可以参考:

http://developer.android.com/guide/topics/search/search-dialog.html

在 Xamarin 下, manifest 文件即 properties 下面的 AndroidManifest.xml



该文件由 Xamarin 自行维护, 不推荐直接修改。

要开启 Activity 的搜索, 可以这样:

1, 新建一个 SearchActivity

[MetaData("android.app.searchable", Resource = "@xml/searchable")]
[IntentFilter(new[] { Intent.ActionSearch })]
[Activity(Label = "SearchActivity", Name = "aA.SearchA")] // aA.SearchA , aA为包名, 必须为小写字母开头
public class SearchActivity : Activity {
    protected override void OnCreate(Bundle bundle) {
        base.OnCreate(bundle);


        if (this.Intent.Action.Equals(Intent.ActionSearch)) {
            var query = this.Intent.GetStringExtra(SearchManager.Query);
            Toast.MakeText(this, query, ToastLength.Short);
        }
    }
}

2,在要使用 Search Dialog 的 Activity 上这样写:

[MetaData("android.app.default_searchable", Value = "aA.SearchA")]
[Activity(Label = "Search", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity {

其中 SearchActivity 上的 Activity 必须指定 Name, 而且必须是 xxx.xxx.xxx 的结构,如果只写 xxx , 编译会报错:缺少 package 名称。



package 名称必须以小写字母开头, 否则打包的时会报错:



在需要使用 Search Dialog 的 Activity 上 (上面示例中的的 MainActivity)的 MetaData 中指定 Value 为 SearchActivity 的 Name (aA.SearchA)

费这个劲加这个 Name 是因为 :

http://developer.xamarin.com/guides/android/advanced_topics/working_with_androidmanifest.xml/

Activity Name

Beginning with Xamarin.Android 5.1, the type name of an activity is based on the MD5SUM of the assembly-qualified name of the type being exported. This allows the same fully-qualified name to be provided from two different assemblies and not get a packaging error. (Before Xamarin.Android 5.1, the default type name of the activity was created from the lowercased namespace and the class name.)

If you wish to override this default and explicitly specify the name of your activity, use the Name property:

在 Xamarin.Android 5.1 以后, activity 的名字会被 MD5SUM 处理, 像这样:



编译后, 可以查看 obj\Debug\android\AndroidManifest.xml 的最终结果。

如何获取输入搜索内容

要使用 Search Dialog 可见,需要在启用了 Search Dialog 的 Activity 上触发:onSearchRequested 方法。

private void Btn_Click(object sender, EventArgs e) {
    this.OnSearchRequested();
}

有些 Android 机, 会自带一个 搜索按钮, 点这个按钮,也是一样会调用这个 OnSearchRequested.

当用户执行搜索的时候,系统会创建一个 Intent 存储用户的查询。

在 SearchActivity 中,

if (this.Intent.Action.Equals(Intent.ActionSearch)) {
    var query = this.Intent.GetStringExtra(SearchManager.Query);
    Toast.MakeText(this, query, ToastLength.Short);
}

如果想传递额外的参数到 SearchActivity, 可以这样传递:

public override bool OnSearchRequested() {
            var bundle = new Bundle();
            bundle.PutBoolean("Key1", true);
            this.StartSearch(null, false, bundle, false);
            return true;
        }

获取额外参数可以这样:

var bundle = intent.GetBundleExtra(SearchManager.AppData);
if (bundle != null) {
    var key1 = bundle.GetBoolean("Key1");
    var key2 = bundle.GetBoolean("Key2");
}



Android搜索框架开发


Android是google的产品,所以自然是少不了搜索。先看看Android一些应用中的搜索对话框。


s1

图1 Android中的全局搜索


s2

图2 联系人搜索


s3

图3 音乐搜索

以上都是通过按下实体键盘上的搜索按钮弹出的一个搜索对话框,当然搜索关键词提示是少不了的。如何实现呢?慢慢来!呵呵。

一、配置搜索描述文件

在res中的xml文件加创建sreachable.xml,内容如下:

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
        android:label="@string/search_label"
        android:hint="@string/search_hint"
        android:searchSettingsDescription="@string/settings_description">

二、创建SearchableMusicActivity.java

至少需要实现onCreate方法显示出来吧。

三、配置AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.halzhang.android.search" android:versionCode="1"
    android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".SearchableMusicActivity"
            android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar">
            <intent-filter>
               <action android:name="android.intent.action.MAIN" />
               <category android:name="android.intent.category.LAUNCHER" />
           </intent-filter>
           <intent-filter>
           <!-- 配置action -->
               <action android:name="android.intent.action.SEARCH" />
           </intent-filter>
           <!-- 指定搜索的配置文件 -->
           <meta-data android:name="android.app.searchable"
               android:resource="@xml/searchable" />
       </activity>
       <meta-data android:name="android.app.default_searchable"
           android:value=".SearchableMusicActivity" />

通过以上三步就能实现搜索对话框了。

device


我们知道做安卓app开发的朋友经常会用到一个抓包了,而我们要介绍的fidder是一个非常不错的工具了,下面一起来看Fiddler抓包记录的工具使用详解
做Android等应用开发离不了抓包工具,Fiddler 是一个相当不错的抓取http/https 的工具了

 

下载的话直接百度搜索Fiddler 下载即可:

 

在手机里的wifi里面设置代理ip和端口即可完成监听

 

安装完成注意设置:
findler4

 

我们一起来看一篇Android Studio Git第一次提交的例子吧,希望此例子能够对各位朋友带来有效的帮助哦。
终于决定开始用android studio 写程序了,遇到的第一件事就是如何向git提交代码。下面整理一下第一次提交的过程。

 

1.打开AS的配置面板,找到Git的选项

 

无标题

 

2.在右边的 Path to Git executable 找到Git的可执行程序,点击右边的Test按钮。出现下图说明成功了。

 

1

 

3.找到VCS菜单, 选择 Enable Version Control Integration,在下拉框中选择 Git , 点击 OK,Module 中的文件都变成了红色。

 

4.选择项目,右键-》git->add,然后再选择 Commit Directory..,如下图,Commit Message 这里是必填项。点击 Commit 按钮。然后选择Push…

 

10400400

 

5.第一次是需要先设置远程主机的, 将Git@OSC仓库的HTTPS连接拷贝到URL里面。

 

10400402

 

6.最后选择需要提交的项目,点击Push按钮即可
[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • C#创建自定义控件及添加自定义属性和事件使用实例详解

    这篇文章主要给大家介绍了关于C#创建自定义控件及添加自定义属性和事件使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
  • JS实现自定义简单网页软键盘效果代码

    本文实例讲述了JS实现自定义简单网页软键盘效果。分享给大家供大家参考,具体如下:这是一款自定义的简单点的网页软键盘,没有使用任何控件,仅是为了练习JavaScript编写水平,安全性方面没有过多考虑,有顾虑的可以不用,目的是学...2015-11-08
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • Vue 组件复用多次自定义参数操作

    这篇文章主要介绍了Vue 组件复用多次自定义参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • 深入解析WordPress中加载模板的get_template_part函数

    这篇文章主要介绍了WordPress中加载模板的get_template_part函数,其中重点讲解了其函数钩子的使用,需要的朋友可以参考下...2016-01-14
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • 自定义jquery模态窗口插件无法在顶层窗口显示问题

    自定义一个jquery模态窗口插件,将它集成到现有平台框架中时,它只能在mainFrame窗口中显示,无法在顶层窗口显示. 解决这个问题的办法: 通过以下代码就可能实现在顶层窗口弹窗 复制代码 代码如下: $(window.top.documen...2014-05-31
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • 自定义feignClient的常见坑及解决

    这篇文章主要介绍了自定义feignClient的常见坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-20
  • pytorch 自定义卷积核进行卷积操作方式

    今天小编就为大家分享一篇pytorch 自定义卷积核进行卷积操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-05-06
  • mysql配置模板(my-*.cnf)参数详细说明

    mysql安装成功后有几个默认的配置模板,列表如下: my-huge.cnf : 用于高端产品服务器,包括1到2GB RAM,主要运行mysql my-innodb-heavy-4G.ini : 用于只有innodb的安装,最多有4GB RAM,支持大的查询和低流量 my-large.cnf : 用于...2015-03-15
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02