一.View及其子类
1.view类
view类继承至 Object 实现了 Drawable.Callback KeyEvent.Callback AccessibilityEventSource接口.
直接子类有:AnalogClock, ImageView, KeyboardView, MediaRouteButton, ProgressBar, Space, SurfaceView, TextView, TextureView, ViewGroup, ViewStub
间接子类无数:AbsListView, AbsSeekBar, AbsSpinner, AbsoluteLayout, ActionMenuView, AdapterView<T extends Adapter>, AdapterViewAnimator, AdapterViewFlipper...
2.view是所有交互式组件的基类.常见View我将它分为三种:
a.普通视图->可见组件:如Button,ImageView这种可以直接看见的,形状确定的静态试图.findviewbyid(R.id.)可以在一个root视图或容器视图中,查找并返回一个组件视图活子容器视图.
b.视图容器:不可以直接看见的,它提供试图的布局及其显示效果等.这些视图以viewgroup为基类.viewgroup是view的子类,是所有容器视图的基类,如所有布局视图, ShadowOverlayContainer,Toolbar,ViewPager,ListView...
((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflater.inflate(R.layout.main, null)通常可以通过xml文件建立一个布局视图root对象.
c.普通视图+视图容器=AdapterView:既有可见视图,又有可见视图的布局.这些视图以AdapterView为基类.AdapterView是viewgroup的子类,是所有需要一个适配器adapter的试图的基类,如AdapterViewFlipper, AppCompatSpinner, ExpandableListView, Gallery, GridView, ListView, Spinner, StackView...;Adapter没有基类及接口实现,是所有适配器的基类.与AdapterView配合使用,Adapter提供数据到Item内部的布局的映射关系,控制单个Item;AdapterView提供Item之间的布局呈现.子类有BaseAdapter,ArrayAdapter<T>,CursorAdapter,HeaderViewListAdapter,ListAdapter,ResourceCursorAdapter,SimpleAdapter...
Viewxxx一般是视图容器,xxxView一般是可见试图. 而gridview,listview这种既是容器又是可见视图.
3.MVC编程思想
在android图形化界面中,大量的使用了多线程的编程方式.视图是用来呈现数据的,可以理解为数据的后台线程.那么如何将数据呈现在view上,我们就需要在修改数据之后,通过视图数据发生了改变.一般的view使用setText,setcolor等接口携带数据的同时就可以通知视图数据发生了改变;但是容器视图的数据改变与通知数据改变是分离的,要想该变视图的一切东西,都需要通过改变适配器指向的数据来完成,然后使用notifyDataSetChanged通知数据发生了改变. 而数据的改变通常需要接受外部的刺激来完成,比如按钮点击,滑动,接收到socket等.
二.AdapterView及Adapter 参考:http://blog.csdn.net/lizzywu/article/details/17612789
1.AdapterView
目的:测量各个Item的总布局宽高,提供所有Item之间的布局及事件监控.
1.1常见子类AdapterView:ListView,Spinner下拉列表,GridView网格图,Gallery缩略图已经被水平的ScrollView和ViewPicker取代,但也还算常用,是一个可以把子项以中心锁定,水平滚动的列表
1.2常见事件
◆点击事件:为列表加载setOnItemClieckListener监听,重写onItemClick(发生单击事件的列表对象ListView,被单击Item的rootview,在列表中的位置position,被单击列表项的行ID)方法。
◆长按事件:为列表加载setOnItemLongClieckListener监听,重写onItemLongClick(发生单击事件的列表对象ListView,被单击Item的rootview,在列表中的位置position,被单击列表项的行ID)方法。
◆重要的方法:onmeasure:和子视图的onmeasure一起决定了itemview循环缓冲区的大小,决定了能同时创建和保存的itemview的个数.所以也可以知道onmeasure的大小跟屏幕的大小页没有多大关系.
第二个参数通常是Item的布局视图对象.注意:这里设置的监听器是该Item的监听器,而不是该Item里面的控件的监听器.
◆onmeasure的3种模式为:MeasureSpec.EXACTLY 精确模式,即父视图希望子视图直接使用推荐的宽高,或子视图告知父视图需要使用的精确宽高;MeasureSpec.AT_MOST 父视图向子视图推荐最多使用的宽高,常见于layout_wrapcontent时的推荐高度,或子视图告知父视图最多使用的宽高,实际宽高由布局使用的layout_wrapcontent决定;MeasureSpec.UNSPECIFIED 可以随意指定视图的大小. 对于这3种模式都是根据父子视图的布局文件来自动选择模式的.重载onmeasure时,可以手动指定模式.
2.Adapter
目的:根据列表数据,生成使用数据特例化的当前在屏幕上可以显示的那些Item的根视图,并通过getchildat来返回这些已经创建Itemview(通常是布局视图,当然也可以是被数据特例的其他视图),在getview中通过向上转型的方式返回view.注意:如果Adapter管理100个数据,但是当前只有50个数据能在onmeasure的范围内(该范围与屏幕大小无关,该范围决定了需要循环缓存区中子对象的个数)显示(假如为第40-90个数据)能在onmeasure的范围内呈现(也许这50个数据不能完全在屏幕上呈现),那么只有第40-90个item的视图对象是被创建,并被Adapter管理着的.没被创建的对象通过Adapter.getchildat()将返回null对象.在屏幕上不可见的视图对象,在代码里面是也不一定不能访问该view对象,关键是该view子对象是否能被包含在其父对象视图或视图容器的onmeasure()方法决定范围内,这当然也与onmeasure的三种模式有关.
1.1常见子类
(1)ArrayAdapter:他只能处理列表项内容全是文本的情况。
◆数据源:数组或者List<String>对象或者其他
(2)SimpleAdapter: 他不仅可以处理列表项全是文本的情况,当列表项中还有其他控件时,同样可以处理。
◆数据源:只能为List<Map<“键”,“值”>>形式的数据
(3)自定义Adapter:根据xml文件中定义的样式惊醒列表项的填充,适用性最强。
(4)SimpleCursorAdapter:专门用于把游标中的数据映像到列表中(我们以后再来研究)
1.2自定义Adapter
(1)创建类,继承自BaseAdapter,或者BaseExpandableListAdapter
(2)重写其中的四个方法
①int getCount():返回的是数据源对象的个数,即列表项数
②Object getItem(int position):返回指定位置position上的列表
③long getItemId(int position):返回指定位置处的行ID
④View getView(int position, View convertView, ViewGroup parent):返回列表项对应的root视图,方法体中
◆实例化视图填充器
◆用视图填充器,根据Xml文件,实例化视图
◆根据布局找到控件,并设置属性
◆返回Item的特定View视图,从而可以呈现在position位置的数据
◆调用时机:该item能被包含在onmeasure的范围内时,将被创建,并添加到保存itemview的循环缓冲区,超出onmeasure范围的itemview视图对象将被移除,listview,gridview都自带回收机制.
关于第二个参数:convertView :The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).是对前一次position位置创建的View的复用,我想是提高效率减少垃圾回收的一种方式.
LayoutInflater.from(context).inflate(R.layout.expandlistview_childgridviewitem, null);可以根据一个布局文件生成一个View视图,从而应用于adapterview的Item.
下面是一个expandablegridview的适配器:
class MyAdapter extends BaseExpandableListAdapter {
//得到子item需要关联的数据
@Override
public Object getChild(int groupPosition, int childPosition) {
String key = Caseproperty.get(groupPosition);
return (Caseproperty_Child.get(key).get(childPosition));
}
//得到子item的ID
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) SelectCaseSet.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.expandlistview_childgridview, null);
}
GridView gridView = (GridView) convertView .findViewById(R.id.gridView);
String key = Caseproperty.get(groupPosition);
ArrayAdapter<String> adapter=new ArrayAdapter<String>(SelectCaseSet.this,R.layout.expandlistview_childgridviewitem,R.id.child_textview,Caseproperty_Child.get(key));
gridView.setAdapter(adapter);
return convertView;
}
//获取当前父item下的子item的个数
@Override
public int getChildrenCount(int groupPosition) {
return 1;
}
//获取当前父item的数据
@Override
public Object getGroup(int groupPosition) {
return Caseproperty.get(groupPosition);
}
@Override
public int getGroupCount() {
return Caseproperty.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
//设置父item组件
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) SelectCaseSet.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.expandlistview_parentitem,null);
}
TextView tv = (TextView) convertView
.findViewById(R.id.parent_textview);
tv.setText(SelectCaseSet.this.Caseproperty.get(groupPosition));
return tv;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
}
3.数据填充
3.1:声明AdapterView对象,根据ID利用findViewById方法找到此对象
3.2:声明Adapter对象,根据构造方法实例化此对象。具体如下:
(1)ArrayAdapter<数据类型> adapter = new ArrayAdapter<数据类型>(context:一般指当前Activity对象,layout:每个列表项显示的布局,data:数据源变量);
(2)SimpleAdapter adapter = new SimpleAdapter(context:一般指当前Activity对象,data:数据源变量,layout:每个列表项显示的布局,new String[]{}:数据源中的“键”,new int[]{}:显示数据源的控件ID);
(3)自定义Adapter类 adapter = new 自定义Adapter类构造方法;
3.3:绑定Adapter对象到Adapter上:AdapterView对象.setAdapter(Adapter对象);
4.动态数据增,插,删,排序
Adapter可以进行动态的数据变更,使用add(), insert(), remove(), sort(),要求保存数据的容器具有该4中方法(数组array是不可以的比如String[8],list可以);否则系统会报告Java.lang.NullPointerException。能够进行动态调整的前提是数据源可伸缩的,即size可以调整.
List<String> planents = new ArrayList<String>(); //采用ArrayList的可伸缩大小的数据格式所谓数据源
initPlanets( planets) ; //对List的数据源进行初始化设置
adapter= new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, planents);
this.setListAdapter(adapter);
……
......
adapter.add("Pulto");
adapter.notifyDataSetChanged(); //在此例子中,无需加上这句也能生效,但仍建议加上,以确保同步;这样listview就会重新布局了.
注意数据的改变是指adapter绑定的数据链表引用指向的内存空间的数据的变化,可以以任何方式改变该数据,而不一定使用adapter的增,插,删,排序接口,最后需要调用adapter.notifyDataSetChanged()通知其父窗口重新布局.adapter.notifyDataSetChanged()该函数神奇的地方是:它对屏幕上其他控件的布局也会产生映像,即调用之后将重新布局整个屏幕.
延伸到任何容器视图如布局等,在删除了他们的任意位置的某个子控件后,整个屏幕都将会被重新布局.不管是否是在该容器视图内部或者外部的控件或其他布局,都会被重新计算布局位置.
三.多个expandable的控件嵌套
1.当多个可展开的VIEW嵌套时,通常出现问题:内部的可展开控件(如gridview,listview等)通常只能同时显示1-2行.
比如容器视图ScrollView中嵌套多个gridview或listview时,当满屏时,每个gridview通常只能显示一行,然后在各自的空间中可滚动显示.这显然不满足我们的要求.
可以重新gridview的onMeasure,设置为尽可能高
public class MyGridView extends GridView{
public MyGridView(android.content.Context context,
android.util.AttributeSet attrs)
{
super(context, attrs);
}
/**
* 设置不滚动,高度完全展开
*/
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)//传入的是父布局给GRIDVIEW推荐使用的宽高,这可能是不能满足被嵌入组件的需求
{
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, //AT_MOST模式,最大使用Integer.MAX_VALUE/4的高度布局,意思是"尽可能高"
MeasureSpec.AT_MOST); //有3中宽高指定模式,expandSpec的最高2为表示模式, MeasureSpec.makeMeasureSpec函数正是做了这样的处理.
super.onMeasure(widthMeasureSpec, expandSpec); //最终被嵌入的控件使用的总的布局宽高
}
}
root组件会给其嵌入组件推荐布局宽高,但最终是否使用这个宽高还是由view组件的onmeasure方法自己决定的. 也就是说view组件自己的宽高可以有自己最终决定.
root组件推荐的宽高与所有嵌入组件的宽高设置及root组件的布局方式有关系.
2.动态的增加/删除布局文件中的控件,可以达到"expandable可展开"的效果.
一个activity只能同时设置一个布局文件显示在屏幕上,但是可以建多个布局文件,将这些布局文件通过填充器转换为代码中的视图对象,xml返回的视图对象就是xml的根节点对象. 通过布局文件生成的对象对主xml进行更改.
setContentView(R.layout.activity_main);
LinearLayout t1=((LinearLayout)(findViewById(R.id.lay)));
t1.removeViewAt(2); //当调用该函数时,立即进行重新布局,并显示在屏幕上.删除第2个控件,将映像其后控件的布局位置.
注意又是组件height和weight的属性需要设置为:android:layout_height="wrap_content"
android布局文件中的include语法:包含另一个xml文件<include layout="@layout/content_main"/>
下面是一个完整的xml文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.mapa.myapplication.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main"/> <!--incldue 语法-->
<TextView
android:text="Hello World! 5"
android:id="@+id/t4"
android:onClick="onclick"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
四.视图控件或视图容器在屏幕上布局时所占用屏幕的宽高
1.容器视图中的控件的onmeasure方法和ondraw方法的调用顺序:向调用所有被嵌入控件的onmeasure方法,然后调用外部容器的onmeasure方法;然后调用被嵌入控件的ondraw方法,容器的ondraw方法不会被调用.子控件在ondraw被调用的顺序与容器的布局方式有关,linearlayout当然是依次被调用,而relativelayout就不是了,当控件的位置在屏幕之外的控件的ondraw是不会被调用的;根据布局方式的不同onmeasure在一次显示中可能被多次调用,而ondraw最多被调用一次.再来数理一下:以线性布局为例,依次调用第一个容器中子控件的onmeasure,然后调用第一个容器的onmeasure;第二个容器...;一次调用能在当前屏幕范围内显示的所有控件的ondraw将控件呈现在屏幕上.
2.视图控件或视图容器在屏幕上布局时所占用屏幕的宽高由view的虚方法onmeasure方法决定.
视图容器会根据自己的布局参数(比如xml中的layout自己的参数)首先推荐给其子控件一个布局宽高,然后子控件给其返回一个布局宽高,作为自己最终需要显示的宽高,父容器使用该宽高来计算自己的宽高.
只有一个xml中的根容器的宽高计算出来之后,才能根据屏幕的尺度决定哪些控件的ondraw会被调用,位置在屏幕外的控件的ondraw是不会被调用的.
3.可展开容器嵌套:外层容器向内层容器和组件推荐布局宽高,内层容器和组件根据这个推荐的布局宽高和自己的布局参数,返回给外层容器自己实际需要的布局宽高,外层容器根据自己的布局参数和这些宽高决定总的布局宽高.
4.view是什么?
在Android的官方文档中是这样描述的:这个类表示了用户界面的基本构建模块。一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。View是用来构建用户界面组件(Button,Textfields等等)的基类。ViewGroup子类是各种布局的基类,它是个包含其他View(或其他ViewGroups)和定义这些View布局参数的容器。
内存中的view:同一个窗口的所用view 都存储在一个树内,既可以通过代码动态增加删除view, 也可以通过在xml文件中定义一个view树来构造这个树.
屏幕上的view:view是一个矩形区域,使用左&上的坐标以及长和宽可以表示一个View. 我们可以使用方法getLeft() getTop() getRight() getBottom() getWidth() 等函数来获取其位置信息.
5.view的重绘过程
view在屏幕显示的过程在内存中体现为View树的递归绘制过程,从ViewGroup一直向下遍历,直到所有的子view都完成绘制,那这一切的源头在什么地方(是谁最发起measure、layout和draw的)?当然就是在View树的源头了——ViewRoot,ViewRoot中包含了窗口的总容器DecorView,ViewRoot中的performTraversal()方法会依次调用decorView的measure、layout、draw方法,从而完成view树的绘制。invalidate()方法会导致整个View树的重新绘制,而且view中的状态标志mPrivateFlags中有一个关于当前视图是否需要重绘的标志位DRAWN,也就是说只有标志位DRAWN置位的视图才需要进行重绘。当视图调用invalidate()方法时,首先会将当前视图的DRAWN标志置位,之后有一个循环调用parent.invalidateChildinParent(),这样会导致从当前视图依次向上遍历直到根视图ViewRoot,这个过程会将需要重绘的视图标记DRAWN置位,之后ViewRoot调用performTraversals()方法,完成视图的绘制过程。
5.自定义一个view
基本上需要实现3个方法onMeasure()、onLayout()、onDraw()三个子方法。具体操作如下:
1、measure操作
measure操作主要用于计算视图的大小,即视图的宽度和长度。在view中定义为final类型,要求子类不能修改。measure()函数中又会调用下面的函数:
(1)onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。
2、layout操作
layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:
(1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;
(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;
3、draw操作
draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:
(1)绘制背景;
(2)如果要视图显示渐变框,这里会做一些准备工作;
(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
(6)绘制滚动条;
从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法。
五.Fragment
1.Fragment的生命周期
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。
Fragment的几个重要的生命周期函数:
onAttach(Activity):当Fragment与Activity发生关联时调用。
onCreateView(LayoutInflater, ViewGroup,Bundle):创建该Fragment的视图
onActivityCreated(Bundle):当Activity的onCreate方法返回时调用
onDestoryView():与onCreateView想对应,当该Fragment的视图被移除时调用
onDetach():与onAttach相对应,当Fragment与Activity关联被取消时调用
2.Fragment的使用方式:2种
2.1静态的使用Fragment:把Fragment当成普通的控件,直接写在Activity的布局文件中,这需要重新实现一个属于自己的Fragment,在其生命周期中进行布局及相应设置.同实现Activity的方式一样的.
步骤:
a、继承Fragment,重写onCreateView决定Fragemnt的布局
MyFragment的布局文件:myfragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="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>
public class MyFragment extends Fragment
{
private ImageButton mLeftMenu;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.myfragment, container, false);
return view;
}
}
b、在Activity中声明此Fragment,就当和普通的View一样
Activity的布局文件:activity_main.xml
<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" >
<com.zte.mata.MyFragment
android:id="@+id/id_fragment_title"
android:name="com.zhy.zhy_fragments.TitleFragment"
android:layout_width="fill_parent"
android:layout_height="45dp" />
<com.zte.mata.MyFragment
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>
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
}
2.2动态的使用Fragment:基本方式是使用Fragment视图对象去替换某个布局视图对象中的全部内容.
http://blog.csdn.net/lmj623565791/article/details/37970961
a.Fragment使用上面的,不变:不管动态使用还是静态使用,特例Fragment的实现是不变的.
b.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" />
<include //在屏幕的底部添加4个按钮
android:id="@+id/id_ly_bottombar"
android:layout_width="fill_parent"
android:layout_height="55dp"
android:layout_alignParentBottom="true"
layout="@layout/bottombar" />
<FrameLayout //占位布局,为了使fragment插入到该布局中.
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>
public class MainActivity extends Activity implements OnClickListener //效果类似于微信
{
private LinearLayout mTabWeixin;
private LinearLayout mTabFriend;
private MyFragment mWeixin;
private MyFragment 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); //主界面底部的2个按钮,位于
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 MyFragment();
transaction.replace(R.id.id_content, mWeixin);
transaction.commit();
}
@Override
public void onClick(View v) //点击底部的按钮,切换fragment
{
FragmentManager fm = getFragmentManager();
// 开启Fragment事务
FragmentTransaction transaction = fm.beginTransaction();
switch (v.getId())
{
case R.id.tab_bottom_weixin:
if (mWeixin == null)
{
mWeixin = new MyFragment();
}
// 使用当前Fragment的布局替代id_content的控件
transaction.replace(R.id.id_content, mWeixin); //R.id.id_content甚至可以是根布局的id
break;
case R.id.tab_bottom_friend:
if (mFriend == null)
{
mFriend = new MyFragment();
}
transaction.replace(R.id.id_content, mFriend);
break;
}
// transaction.addToBackStack();
// 事务提交
transaction.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。
3.管理Fragment回退栈 http://blog.csdn.net/lmj623565791/article/details/37992017
类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生(commit)的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。
添加fragment到回退栈的方法:FragmentTransaction.addToBackStack(String)
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,仅仅是设为不可见,并不会销毁销毁view
transaction.show()显示之前隐藏的Fragment
detach()会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
attach()重建view视图,附加到UI上并显示。
transatcion.commit()//提交一个事务
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); //这比直接添加到回退栈更高效,即不销毁fragment实例也不销毁view对象;如果只使用addToBackStack,将销毁view对象.
tx.add(R.id.id_content , fThree, "THREE");
// tx.replace(R.id.id_content, fThree, "THREE");
tx.addToBackStack(null);
tx.commit();
}
}
4.Fragment与Activity通信 http://blog.csdn.net/lmj623565791/article/details/37992017
因为所有的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()
因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。
所以Fragment之间或与activity通讯的基本思路是:Fragment中声明一个接口,然后在activity中继承并实现该接口,fragment通过getactivity方法获得该接口,并调用相应的方法.
5.Fragment如何处理运行时配置发生变化(多activity实例)
运行时配置发生变化,最常见的就是屏幕发生旋转.很多人觉得强制设置屏幕的方向就可以了,但是有一点,当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。比如上例:如果你把上面的例子你至于FragmentThree界面(非activity启动的默认fragment界面),然后处于后台状态,长时间后你会发现当你再次通过home打开时,上面FragmentThree与FragmentOne叠加在一起,这就是因为你的Activity重新启动,在原来的FragmentThree上又绘制了一个FragmentOne。
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();
}
}
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
也就是说:配置改变之后,fragment会重新创建.
这是为什么呢,因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。
那么如何解决呢:
其实通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建:
默认的savedInstanceState会存储一些数据,包括Fragment的实例:通过打印可以看出:
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实例:
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也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。
6.Fragmeny与ActionBar和MenuItem集成
Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。
a、在Fragment的onCreate中调用 setHasOptionsMenu(true);
b、然后在Fragment子类中实现onCreateOptionsMenu
c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了Activity也可以直接处理该MenuItem的点击事件。
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;
}
}
7、没有布局的Fragment的作用
没有布局文件Fragment实际上是为了保存,当Activity重启时,保存大量数据准备的
http://blog.csdn.net/lmj623565791/article/details/37936275
<Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案 >
8.使用Fragment创建对话框
这是Google推荐的方式:各种对话框
http://blog.csdn.net/lmj623565791/article/details/37815413
六.View类可以通过setAnimation来设置动画
最基本的4种动画以及1个动画set:
AlphaAnimation Alpha动画:淡入淡出动画
ScaleAnimation Scale动画:淡入淡出
RotateAnimation Rotate动画:旋转动画
TranslateAnimation Translate动画:移动动画
AnimationSet 动画集合,用于添加多种动画
例子:
AnimationSet As=new AnimationSet(true);
AlphaAnimation Aa=new AlphaAnimation(1,0);
Aa.setDuration(500);
As.addAnimation(Aa);
button.setAnimation(As);
七.Android的消息循环机制
Android的每个线程都允许存在一个独立的消息队列及异步消息循环处理机制.Activity主线程已经在底层实现了该机制,只需要在该主线程中定义Handler及向该Handler发送消息,主线程的消息处理机制就能将消息分发给Handler进行处理. 当然每个线程都可以构造一个消息处理机制,来处理发往该线程中的消息,我们自定义的消息一般用一个单独的线程来处理.主线程一般处理的是UI消息.
7.1任意线程中实现消息循环机制,并向指定线程中发送消息.
主要的类:Handler 、 Looper 、Message
class Rub implements Runnable {
public Handler myHandler;
// 线程体
@Override
public void run() {
Looper.prepare(); //实例化了一个Looper对象及MessageQueue对象,一个线程只能调用一次
myHandler = new Handler() { //在该线程中定义的Handler将绑定该线程中的消息循环队列
@Override
public void handleMessage(Message msg) {
String ms = "";
if (msg.what == 0x123) {
int data = msg.getData().getInt(DATA);
//循环的时候界面依旧可以点击next按钮 这是本实例效果
for (int i = 0; i < data; i++) {
try {
//循环一次 暂停1秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ms += String.valueOf(i) + " ";
Toast.makeText(getApplicationContext(), ms, Toast.LENGTH_LONG)
.show();
}
}
}
};
Looper.loop(); //Looper主要作用: 1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Rub rub = new Rub();
//子线程中不能有UI组件进行操作
th = new Thread(rub);
// 启动线程
th.start();
ok1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(DATA, Integer.parseInt(ed1.getText().toString()));
msg.setData(bundle);
rub.myHandler.sendMessage(msg); //在主线成中可以向任意线程发送消息
//rub.myHandler.sendEmptyMessageDelayed(int what, long delayMillis)
//rub.myHandler.sendMessageDelayed(Message msg, long delayMillis)
//rub.myHandler.post(new Runnable() {//这是直接向消息队列中传入一个具有callback方法的空消息,即直接让LOOPER运行该callback方法,而不会运行handler.handleMessage方法
@Override
public void run()
{
Log.e("TAG", Thread.currentThread().getName());
mTxt.setText("yoxi");
}
});
}
});
}
Handler中分发消息的一些方法
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
总结一下
1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
2、Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue形成关联。
4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。
7.2在Activity的主线程中使用消息队列
下面是将消息直接发送到主线程的消息队列,并调用在主线程中定义的Handler来处理该消息.
public class MyHandlerActivity extends Activity {
Button button;
MyHandler myHandler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handlertest);
button = (Button) findViewById(R.id.button);
myHandler = new MyHandler(); // 当创建一个新的Handler实例时, 它会绑定到当前线程和消息的队列中,开始分发数据
// Handler有两个作用, (1) : 定时执行Message和Runnalbe 对象
// (2): 让一个动作,在不同的线程中执行。
// 它安排消息,用以下方法
// post(Runnable)
// postAtTime(Runnable,long)
// postDelayed(Runnable,long)
// sendEmptyMessage(int)
// sendMessage(Message);
// sendMessageAtTime(Message,long)
// sendMessageDelayed(Message,long)
// 以上方法以 post开头的允许你处理Runnable对象
//sendMessage()允许你处理Message对象(Message里可以包含数据,)
MyThread m = new MyThread();
new Thread(m).start(); //该任意线程中向主线程发送消息,并用myHandler处理
}
/**
* 接受消息,处理消息 ,此Handler会与当前主线程一块运行
* */
class MyHandler extends Handler {
public MyHandler() {
}
public MyHandler(Looper L) {
super(L);
}
// 子类必须重写此方法,接受数据
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
Log.d("MyHandler", "handleMessage。。。。。。");
super.handleMessage(msg);
// 此处可以更新UI
Bundle b = msg.getData();
String color = b.getString("color");
MyHandlerActivity.this.button.append(color);
}
}
class MyThread implements Runnable {
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.d("thread。。。。。。。", "mThread。。。。。。。。");
Message msg = new Message(); //新建一个消息并发送
Bundle b = new Bundle();// 存放数据
b.putString("color", "我的");
msg.setData(b);
MyHandlerActivity.this.myHandler.sendMessage(msg); // 向Handler发送消息,更新UI
}
}
}
八.Android的订阅-消息通知机制
eventbus机制: http://blog.csdn.net/harvic880925/article/details/40660137
otto机制
Android开发使用的常见第三方框架汇总: http://blog.csdn.net/liuhaomatou/article/details/44857005