Android开发中Fragment完全解析【详细】

 更新时间:2016年9月20日 19:55  点击:2145
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的View事件传递,android上view touch事件的传递问题,Android的事件传递机制。

Android的View 事件传递

1、基础知识

(1) 所有 Touch 事件都被封装成了 MotionEvent 对象,包括 Touch 的位置、时间、历史记录以及第几个手指(多指触摸)等。

(2) 事件类型分为 ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以 ACTION_DOWN 开始 ACTION_UP 结束。

(3) 对事件的处理包括三类,分别为传递——dispatchTouchEvent()函数、拦截——onInterceptTouchEvent()函数、消费——onTouchEvent()函数和 OnTouchListener


2、传递流程

(1) 事件从 Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的 View(ViewGroup)开始一直往下(子 View)传递。子 View 可以通过 onTouchEvent()对事件进行处理。

(2) 事件由父 View(ViewGroup)传递给子 View,ViewGroup 可以通过 onInterceptTouchEvent()对事件做拦截,停止其往下传递。

(3) 如果事件从上往下传递过程中一直没有被停止,且最底层子 View 没有消费事件,事件会反向往上传递,这时父 View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到 Activity 的 onTouchEvent()函数。

(4) 如果 View 没有对 ACTION_DOWN 进行消费,之后的其他事件不会传递过来。

(5) OnTouchListener 优先于 onTouchEvent()对事件进行消费。
上面的消费即表示相应函数返回值为 true。


附上两张原文中流程图:
(1) View 不处理事件流程图

20150811162712052.jpeg


(2) View 处理事件流程图
20150811162712052.jpeg


3、最后说几句

Android Touch事件
假设布局层次为
Layout0
Layout1
Layout2
Layout3

如果谁都没有去interceptTouch,同时谁都没有处理onTouch事件。
那么Layout0->intercept Layout1->intercept Layout2->intercept Layout3->intercept
Layout3->onTouch Layout2->onTouch Layout1->onTouch Layout0->onTouch
由于谁都没有消费ACTION_DOWN事件,后续的MOVE,UP事件将不会传进来。

如果Layout2 intercept了,但是不消费onTouch
那么Layout0->intercept Layout1->intercept Layout2->intercept
Layout2->onTouch Layout1->onTouch Layout0->onTouch
后续事件不会传入

如果Layout2 intercept了,同时消费了。
那么 0->intercept 1->intercept 2->intercept 2->onTouch
0->intercept 1->intercept 2->onTouch
0->intercept 1->intercept 2->onTouch
0->intercept 1->intercept 2->onTouch

如果Layout2 intercept了,不消费,Layout1消费了。
那么0->intercept 1->intercept 2->intercept
2->onTouch 1->onTouch
0->intercept 1->onTouch
0->intercept 1->onTouch
0->intercept 1->onTouch

总结一下。规律就是
如果当前Layout intercept了,那么子View和子ViewGroup都没有机会去获得Touch事件了。如果当前Layout并不消费事件的话,这个事件会一直向上冒泡,直到某个父Layout的onTouchEvent消费了这个事件。如果没有任何一个父Layout消费这个事件,那么后续的事件都不会被接受。
如果在冒泡过程中有某个Layout消费了这个事件。那么这个Layout的所有父Layout的intercept仍然会被调用。但是当前Layout的intercept不会再被调用了。直接调用onTouch事件。

另外,对于底层的View来说,有一种方法可以阻止父层的View截获touch事件,就是调用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action。在实践过程中发现ListView在滚动的时候会调用这个方法。使得action不能被拦截。


android上view touch事件的传递问题

项目中要实现拖拽功能,即在scrollview里面放的imageview长按后,有影子被拖走的感觉。实现思路基本是这样的:
1、在布局文件里把imageview放到scrollview中
2、为imageview注册touch监听
3、重写scrollview的onTouchEvent函数。
4、创建由一张imgeview生成的popupwindow
5、通过touch的move更新所创建的popupwindow的位置。实现imageview上的图片被拖走的感觉。
开始时,由imageview响应touch事件,但随着手移出imageview,touch事件就不一定会还是由imageview接收了!那touch事件该传给谁呢?imageview上的touch事件是从哪步溜走了呢?经过尝试发现是经过imageview的cancel事件之后,后面的touch事件都交给了其父类操作,这里就是scrollview。touch事件从imageview消失后直接将后续的move事件和up事件交由了scrollview。我们要实现拖走imageview上图片的效果就可以通过创建popupwindow的方式来实现,所创建的popupwindow只有一张图片组成。通过更新popupwindow显示的位置就可以实现拖拽imageview上图片的效果了。


Android的事件传递机制

Android的事件传递机制分为按键事件和触摸事件,而这里的事件指的是touchevent,即触摸事件。

一个touchevent一般是由多个motionevent(有DOWN,UP,MOVE,CANCEL四种)构成,合理的分配这些motionevent到达指定的控件,这些控件才能够接收到相应的touchevent,然后做出处理。关于motionevent请参考我转的另一篇博文。

 
一.相关类和方法

1.与触摸事件有关系的类是view,viewgroup,activity。

1)这里view我们表示的是那些继承自view不能再容纳其他控件的类,比如textview,imageview。其中下面两个方法是三者都有的,且与touchevent相关的。

2)这里的viewgroup表示的是那些继承自viewgroup的类,它们的共同点是可以继续包含view。比如各种layout以及上面说到的恶心的listview。

3)这里的activity表示的就是那些继承自activity的类。

所以下面没有特殊描述,均用一个类代表它们整个群体。
 

2.与触摸事件有关系的方法是dispatchTouchEvent,onTouchEvent以及onInterceptTouchEvent,

public boolean dispatchTouchEvent(MotionEvent event) - 用于事件分发,三个类都有该方法

public boolean onTouchEvent(MotionEvent event) - 用于事件消费,三个类都有该方法

public boolean onInterceptTouchEvent(MotionEvent ev) - 用于拦截事件,只有viewgroup有该方法

这三个方法在三个类中的用途是一样的,但是详细的处理过程却不同。这我们将在下一部分去说明。

 
二.motionevent的dispatchTouchEvent流程

1.Activity部分

对于正常的理解来说,应该是activity拿到某一个motionevent,然后开始事件分发,所以我们来看看activity的dispatchTouchEvent源码

public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}



 
1)Activity处理事件

对于一个手机程序来说,最先拿到手势的我觉得应该就是window了,所以首先用superDispatchTouchEvent分配motionevent到activity的内部控件,superDispatchTouchEvent实际做的事情就是FrameLayout.dispatchTouchEvent(处理流程如同下文的ViewGroup.dispatchTouchEvent),它将会去查找有没有view可以处理该事件。如果activity没有内部控件或者内部控件无法处理该motionevent时,superDispatchTouchEvent返回false,然后接收该motionevent的就是activity本身了,我们可以在Activity的onTouchEvent方法里做详细的处理。

2)Activity不处理事件

刚才1)中说到,Activity没有内部控件或者内部控件无法处理该motionevent时superDispatchTouchEvent会返回false,但是如果有内部控件切可以处理该motionevent时,将返回true,这时Activity的dispatchTouchEvent也会返回true告知系统我有一个控件接收了motionevent。

 
2.ViewGroup部分

superDispatchTouchEvent将事件进行分发,首先接到的当然是该Activity的Layout控件,它继承自ViewGroup。当它接收到了之后显然也要先考虑事件的分发。我们来看看ViewGroup的dispatchTouchEvent代码

public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
if (target == null) {
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
// if have a target, see if we're allowed to and want to intercept its
// events
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = null;
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}



1)ViewGroup被当成普通的View

整体代码的意思是说有一个motionevent传递过来了,ViewGroup首先看自己的内部View能不能处理或者说哪个View能够处理,判断的方法是看看该motionevent是不是DOWN,如果是则看看View是否可见,如果可见再看看焦点是不是在View的内部,如果在其内部,那么我们说找到了一个View可能处理该motionevent,将其命名为target(We know we want to dispatch the event down, find a child who can handle it, start with the front-most child.),如果说没有一个View可以处理(We don't have a target, this means we're handling the event as a regular view.),此时ViewGroup将被当做普通的View来处理这个motionevent,那么将调用 super.dispatchTouchEvent(ev)方法,这里就是View的dispatchTouchEvent了。return语句返回的就是super.dispatchTouchEvent(ev)

2)ViewGroup内部有View可以处理

如果说ViewGroup内部有View可以处理,假设为target,那么将调用target.dispatchTouchEvent方法。return语句返回target.dispatchTouchEvent(ev)

3)onInterceptTouchEvent

这里有一个非常特殊的方法,就是onInterceptTouchEvent了,它可以让ViewGroup对motionevent进行拦截,意思就是我们发现某个target可以获得DOWN的焦点,但是ViewGroup不想让它内部的View处理事件,则进行拦截,此时dispatchTouchEvent返回true。


3.View部分

View部分在相对就简单一些了,在上面的target.dispatchTouchEvent之后,motionevent被传递到了View的dispatchTouchEvent中,看到这里应该也就明白了motionevent也是类似的从Activity传递到ViewGroup中的,在superDispatchTouchEvent里Framelayout.dispatchTouchEvent找到了某个View(实际是某个Layout的ViewGroup),调用了它的dispatchTouchEvent。

看View源代码中的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}



因为View表示没有任何其他内部的控件了,所以它只有两种选择,这里首先将使用我们开发人员定义的OnTouchListener进行处理,如果可以处理,那么将返回true,如果不能处理将使用默认的onTouchEvent来处理。
 
三.motionevent的onTouchEvent流程

最底层的View的dispatchTouchEvent会调用onTouchListener来进行处理motionevent,或者使用onTouchEvent来处理motionevent,不论哪种都默认会返回true。所以这时ViewGroup的dispatchTouchEvent返回值为true,所以Activity的dispatchTouchEvent的返回值是true。

如果我们没定义自己的onTouchListener,并且重写了onTouchEvent,返回一个false,那么ViewGroup的dispatchTouchEvent返回为false,Activity将会调用它的onTouchEvent方法。

 
四.后续的motionevent

如果motionevent为DOWN的时候View没有处理,即在它的dispatchTouchEvent内返回了false,那么该View的容器ViewGroup不会再调用该View的dispatchTouchEvent了,即它将无法接收到后续的MOVE,UP。只有DOWN的时候被View处理了(在dispatchTouchEvent返回true),后续的MOVE,UP才会传递到该View。

 
五.ListView的问题

想给ListView实现左右滑动翻页的功能,正常是想着使用ViewFillper,但是不用ViewFlipper动态改变adapter的内容再刷新ListView的话应该如何实现呢。有下面的想法

1.给ListView定义一个手势对象gestureDector,重写它的onTouchEvent,在里面使用return gestureDector.onTouchEvent。gestureDector的手势监听器默认在onDown的时候返回false,所以一个DOWN的motionevent传过来,onTouchEvent返回false,根据上面的说法,dispatchTouchEvent也将返回false,后续motionevent将不会再传递到ListView,失败。

2.重写手势监听器的onDown返回值为true,这时可以实现左右翻页,但是ListView本身的onItemClickListener将没办法正常工作。失败。

3.重写dispatchTouchEvent,调用super.dispatchTouchEvent,然后始终返回true。重写onTouchEvent,首先调用gestureDector.onTouchEvent,如果返回为false,说明gestureDector.onTouchEvent没有处理该事件,我们的左右滑动也没有触发,那么return super.onTouchEvent处理,包括它的onItemClickListener等等都可以正常运行。不管super.onTouchEvent返回何值,因为dispatchTouchEvent返回了true,所以后续的动作都会传来。如果返回为true,说明gestureDector.onTouchEvent处理了左右滑动事件(前提是在手势监听器里面fling动作返回了true),此时return true。成功。

4.重写dispatchTouchEvent,一开始就调用gestureDector.onTouchEvent,然后同样的处理方式,最终保证能够return true。

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

结论

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

◆清晰友好的入口界面

◆便于用户理解及掌握

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

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


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

 

[!--infotagslink--]

相关文章

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

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • JavaScript预解析,对象详解

    这篇文章主要介绍了JavaScript预解析,对象的的相关资料,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助...2021-11-10
  • php常量详细解析

    一、常量常量是一个简单值的标识符(名字)。如同其名称所暗示的,在脚本执行期间该值不能改变(除了所谓的魔术常量,它们其实不是常量)。常量默认为大小写敏感。按照惯例常量标识符总是大写的。 常量名和其它任何 PHP 标签遵循...2015-10-30
  • Android开发中findViewById()函数用法与简化

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

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神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
  • Android自定义WebView网络视频播放控件例子

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

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • vscode搭建STM32开发环境的详细过程

    这篇文章主要介绍了vscode搭建STM32开发环境的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-02
  • 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
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 安卓开发之Intent传递Object与List教程

    下面我们一起来看一篇关于 安卓开发之Intent传递Object与List的例子,希望这个例子能够为各位同学带来帮助。 Intent 不仅可以传单个的值,也可以传对象与数据集合...2016-09-20
  • JS跨浏览器解析XML应用过程详解

    这篇文章主要介绍了JS跨浏览器解析XML应用过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-16