Android 图片轮播实现及获取Android的资源信息
在手机app应用中我们经常会看到图片轮播动画效果,Android中想要实现图片轮播,主要用到ViewPager这个控件来实现,这个控件的主要功能是实现图片的滑动效果。
那么有了滑动,在滑动的基础上附上图片也就实现了图片轮播的效果...这个控件类似于ListView,需要使用到适配器这个东西,适配器在这里的作用是为轮播时设置一些效果...这里需要使用到PagerAdapter适配器...下面来一个例子,这个例子的效果是在图片轮播的同时显示播放的是第几张图片的信息...并且下面的点也是会随之进行变化的...
先上一下布局文件的代码...这个布局文件其实还是有点说道的...这句话必须要引进...否则会出现错误...意思就是我设置了一个滑动的效果,这个效果填充整个FrameLayout...每一个View表示一个控件,这个控件的显示方式在另外的xml文件当中...下面是两个xml文件...
上面通过配置xml文件来完成View的显示方式,因为这五个点的形状,大小,甚至是显示方式基本都是相同的,如果再去找5个点图片或者是一个点图片,然后通过Drawable资源的调用完成图片的显示...通过加载5次的方式...这样显然是没有必要的,会浪费不必要的资源..因此我们可以使用xml提供的自定义图形来完成这个过程...xml为我们提供了shape属性,自定义控件..这里我定义了一个实心圆...这个实心圆来完成随着图像的滑动,这个点也随之进行相应的变化...看起来并不是什么难理解的东西...
重要的部分还是如何去实现这个过程...这个过程的实现就再下面的代码中,详细解释也在代码当中...
package com.example.picture_change; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.annotation.SuppressLint; import android.app.Activity; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /* * HashMap存储的键是不允许重复的...但是值是可以重复的... * * */ public class MainActivity extends Activity { ArrayList imageSource=null; //存放图像控件... ArrayList dots=null; //存放5个点... int []images=null; //存放图像的资源... String []titles=null; //伴随着图像的变动,标题也会随之变动... TextView tv=null; //TextView来来显示title的变化... ViewPager viewpager; //ViewPager来完成滑动效果... MyPagerAdapter adapter; //适配器... Mapmap=new HashMap(); @SuppressLint("UseSparseArrays") MapmapValues=new HashMap(); private int curr=0; private int old=0; int o=0; int mapsize; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toast.makeText(MainActivity.this, "a", Toast.LENGTH_LONG).show(); /* 下面利用反射来完成Drawable的资源获取... * 在这里我获取了5张图片的资源数据..这5张图片分别为a.jpg b.jpg c.jpg d.jpg e.jpg * 这里使用了一个length<=1来完成数据的获取...其实这个方式并不好,是我自己想出来的... * 暂时没有更好的方法...我这里使用反射的目的在下面会进行介绍... * */ Field [] field=R.drawable.class.getFields(); for(Field f:field){ if(f.getName().length()<=1){ try { o++; String str="image"+"_"+o; map.put(str, f.getInt(R.drawable.class));//使用map以键值对的形式来保存图片的数据资源... } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } mapsize=map.size()-1; /* 这里我再次使用了一个map以键值对的形式只保存上一个map的Value值... * 这么做的目的在下面进行说明... * * */ for(Entry entry:map.entrySet()){ mapValues.put(mapsize, entry.getValue()); mapsize--; } init(); } public void init(){ //数据信息的初始化... images=new int[]{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e}; titles=new String[]{"this is the one picture","this is two picture","this is three picture","this is four picture","this is five picture"}; imageSource=new ArrayList(); //这里初始化imageSource... for(int i=0;i<images.length;i++){ ImageView iamgeview =new ImageView(this); iamgeview.setBackgroundResource(images[i]); imageSource.add(iamgeview); } //这里使用了一个方法...我们没有必要一次一次的findViewById()...使用下面的方法很有效的解决了多次findViewById()函数的引用... dots=new ArrayList(); for(int j=0;j<5;j++){ String dotid="dot"+"_"+j; int resId=getResources().getIdentifier(dotid, "id", "com.example.picture_change"); dots.add(findViewById(resId)); } tv=(TextView) findViewById(R.id.tv); tv.setText(titles[0]); viewpager=(ViewPager) findViewById(R.id.vp); adapter=new MyPagerAdapter(); //这里定义了一个适配器对象... viewpager.setAdapter(adapter); //传递对象,绑定适配器... viewpager.setOnPageChangeListener(new onpagelistener()); //这里设置了一个当图片发生滑动后的一个监听效果... ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor();//这里我们开启一个线程... scheduled.scheduleAtFixedRate(new Runnable() { @Override public void run() { // TODO Auto-generated method stub curr=(curr+1)%images.length; handler.sendEmptyMessage(0);//将信息发送给Handler,让Handler处理数据,完成一些操作... } }, 2, 2, TimeUnit.SECONDS); //实现内部方法,设置播放时间... } private class MyPagerAdapter extends PagerAdapter{ @Override public int getCount() { // TODO Auto-generated method stub return images.length; } @Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub //判断前后两张的显示图片是否相同... return arg0==arg1; } @Override public void destroyItem(ViewGroup container, int position, Object object) { //销毁...释放内存... container.removeView(imageSource.get(position)); } @Override public Object instantiateItem(ViewGroup container, int position) { /* 这个方法表示的是滑动到了第几张图片的定位...通过传递一个ViewGroup来完成数据的传递... * 我们上面使用到了一个Map来保存上一个Map的Value值,这个的真正目的就在这里..目的是为了 * 获取当前显示图片的资源信息..说白了就是要获取(R.drawable.属性),为什么要实现这个目的 * 因为我们要实现,当这个显示的图片被点击的时候,我们应该进行哪些操作... * */ ImageView v=imageSource.get(position);//获取当前图片... //position是从0-4的值...因此可以获取到Map中的值了... v.setClickable(true); //设置图片是可以点击的... final int values=(Integer)mapValues.get(position); //这里我们获取map中保存的Values值... System.out.println(values); //下面就是实现触发图片时的监听... v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub switch(values){ case R.drawable.a: Toast.makeText(MainActivity.this, "a", Toast.LENGTH_LONG).show(); break; case R.drawable.b: Toast.makeText(MainActivity.this, "b", Toast.LENGTH_LONG).show(); break; case R.drawable.c: Toast.makeText(MainActivity.this, "c", Toast.LENGTH_LONG).show(); break; case R.drawable.d: Toast.makeText(MainActivity.this, "d", Toast.LENGTH_LONG).show(); break; case R.drawable.e: Toast.makeText(MainActivity.this, "e", Toast.LENGTH_LONG).show(); break; } } }); container.addView(imageSource.get(position)); //将所有的图片都加载到了container中... return imageSource.get(position); } } //定义一个内部类实现图片在变化的时候的监听... class onpagelistener implements OnPageChangeListener{ @Override public void onPageScrollStateChanged(int arg0) { // TODO Auto-generated method stub } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { // TODO Auto-generated method stub } @Override public void onPageSelected(int arg0) { // TODO Auto-generated method stub //当发生滑动后,要完成的一些相应操作... tv.setText(titles[arg0]); dots.get(arg0).setBackgroundResource(R.drawable.dot); dots.get(old).setBackgroundResource(R.drawable.dot_1); old=arg0; curr=arg0; } } @SuppressLint("HandlerLeak") private Handler handler=new Handler(){ public void handleMessage(Message msg) { //接收到消息后,更新页面 viewpager.setCurrentItem(curr); }; }; @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
这里我使用了反射机制来获取Drawable的图像资源,然后通过switch方法来完成了当图片被点击的时候需要完成的操作...这是笔者我自己想出来的一种方法...因为Android没有提供ImageClickListener()这类的方法,因此我们只能够自己去进行书写图片被点击的方法...至于更好的方法,我是还没有发现...也是确实是能力有限制了,这个方法今天也想了整整一个下午才折腾出来的...
注意:给自己的一个提醒,HashMap的键是绝对不能够重复保存的...但是值是可以保存重复的数据的,如果保存了重复的键,那么在map只会保存第一个数据,不会对后续数据进行保存...这个也是一个很大的注意点,自己就栽这里很久,虽然很低级的错误,但是很有可能在不注意的情况下就犯下了...因此在这里也算是给自己提个醒...下次不会再犯下这样的错误的
Android图片轮播效果的几种实现方法
第一种:使用动画的方法实现:(代码繁琐)
这种发放需要:两个动画效果,一个布局,一个主类来实现,不多说了,来看代码吧:
public class IamgeTrActivity extends Activity { /** Called when the activity is first created. */ public ImageView imageView; public ImageView imageView2; public Animation animation1; public Animation animation2; public TextView text; public boolean juage = true; public int images[] = new int[] { R.drawable.icon, R.drawable.expriment, R.drawable.changer, R.drawable.dataline, R.drawable.preffitication }; public int count = 0; public Handler handler = new Handler(); public Runnable runnable = new Runnable() { @Override public void run() { // TODO Auto-generated method stub AnimationSet animationSet1 = new AnimationSet(true); AnimationSet animationSet2 = new AnimationSet(true); imageView2.setVisibility(0); TranslateAnimation ta = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f); ta.setDuration(2000); animationSet1.addAnimation(ta); animationSet1.setFillAfter(true); ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f); ta.setDuration(2000); animationSet2.addAnimation(ta); animationSet2.setFillAfter(true); //iamgeView 出去 imageView2 进来 imageView.startAnimation(animationSet1); imageView2.startAnimation(animationSet2); imageView.setBackgroundResource(images[count % 5]); count++; imageView2.setBackgroundResource(images[count % 5]); text.setText(String.valueOf(count)); if (juage) handler.postDelayed(runnable, 6000); Log.i(handler, handler); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); imageView = (ImageView) findViewById(R.id.imageView); imageView2 = (ImageView) findViewById(R.id.imageView2); text=(TextView)findViewById(R.id.text); text.setText(String.valueOf(count)); //将iamgeView先隐藏,然后显示 imageView2.setVisibility(4); handler.postDelayed(runnable, 2000); } public void onPause() { juage = false; super.onPause(); } }
布局代码:
android:orientation=vertical android:layout_width=fill_parent android:layout_height=fill_parent android:id=@+id/rl> android:id=@+id/imageView android:layout_width=fill_parent android:background=@drawable/icon android:layout_below=@+id/rl android:layout_height=120dp /> android:id=@+id/imageView2 android:layout_width=fill_parent android:background=@drawable/expriment android:layout_below=@+id/rl android:layout_height=120dp /> android:id=@+id/text android:layout_width=fill_parent android:layout_height=wrap_content android:layout_below=@id/imageView/>
第二种:使用ViewFlipper实现图片的轮播
Android系统自带的一个多页面管理控件,它可以实现子界面的自动切换:
首先 需要为ViewFlipper加入View
(1) 静态导入:在layout布局文件中直接导入
(2) 动态导入:addView()方法
ViewPlipper常用方法:
setInAnimation:设置View进入屏幕时候使用的动画
setOutAnimation:设置View退出屏幕时候使用的动画
showNext:调用该函数来显示ViewFlipper里面的下一个View
showPrevious:调用该函数来显示ViewFlipper里面的上一个View
setFlipInterval:设置View之间切换的时间间隔
startFlipping使用上面设置的时间间隔来开始切换所有的View,切换会循环进行
stopFlipping:停止View切换
讲了这么多,那么我们今天要实现的是什么呢?
(1) 利用ViewFlipper实现图片的轮播
(2) 支持手势滑动的ViewFlipper
我们需要先准备几张图片:把图片放进drawable中
创建两个动画:在res下面新建一个folder里面新建两个xml:
left_in:
android:duration=5000 android:fromXDelta=100%p android:toXDelta=0/>
left_out:
android:fromXDelta=0 android:toXDelta=-100%p android:duration=5000/>
一个布局文件:
xmlns:tools=http://schemas.android.com/tools android:layout_width=match_parent android:layout_height=match_parent tools:context=.MainActivity > android:id=@+id/flipper android:layout_width=fill_parent android:layout_height=fill_parent/>
一个主类:
public class MainActivity extends Activity { private ViewFlipper flipper; private int[] resId = {R.drawable.pc1,R.drawable.pc2,R.drawable.pc3,R.drawable.pc4}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); flipper = (ViewFlipper) findViewById(R.id.flipper); /* * 动态导入的方式为ViewFlipper加入子View * */ for (int i = 0; i < resId.length; i++) { flipper.addView(getImageView(resId[i])); } /* * 为ViewFlipper去添加动画效果 * */ flipper.setInAnimation(this, R.anim.left_in); flipper.setOutAnimation(this, R.anim.left_out); flipper.setFlipInterval(5000); flipper.startFlipping(); } private ImageView getImageView(int resId){ ImageView image = new ImageView(this); image.setBackgroundResource(resId); return image; } }
那么这样就实现了一个图片轮询的功能效果了
我们还可以添加点击,滑动效果:
我们还需要添加两个向右的滑动效果:
right_in:
android:fromXDelta=0 android:toXDelta=-100%p android:duration=2000/>
right_out:
android:fromXDelta=100%p android:toXDelta=0 android:duration=2000/>
然后我们还需要在主类里面添加(如果你不想让图片自动播放,只想通过手势来实现图片播放那么你需要把“为ViewFlipper添加动画效果的代码”删掉):
public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); break; case MotionEvent.ACTION_MOVE://判断向左滑动还是向右滑动 if (event.getX() - startX > 100) { flipper.setInAnimation(this, R.anim.left_in); flipper.setOutAnimation(this, R.anim.left_out); flipper.showPrevious(); }else if (startX - event.getX() > 100) { flipper.setInAnimation(this, R.anim.right_in); flipper.setOutAnimation(this, R.anim.right_out); flipper.showNext(); } case MotionEvent.ACTION_UP: break; } return super.onTouchEvent(event); }
这样我们利用我们的ViewFlipper完成的图片轮询的功能就做完了。
Android的UI组件都是继承View类,View表示一个空白的矩形区域。TextView、Button、EditText这些常用的组件等都是直接或间接继承自View。
此外,View还有一个重要的子类ViewGroup,该类可以用来包含多个View组件,本身也可以当做一个View组件被其他的ViewGroup所包含,由此,可以构建出非常复杂的UI界面。
常用的布局管理器如FrameLayout、LinearLayout、RelativeLayout等都直接继承自ViewGroup。
在Android应用中,Activity就相当于传统桌面开发中的Form,刚创建出来就是一个空白的屏幕,因此,要显示UI界面时,就需要调用setContentView()方法传入要显示的视图实例或者布局资源。
如:
传入一个布局资源:
setContentView(R.layout.main);
传入一个View实例:
TextView myTv = new TextView(this);
setContentView(myTv);
myTv.setText(“hello, world”);
因为setContentView()只能接受一个View实例,要显示复杂的UI界面,就需要用到ViewGroup来包含多个多个View实例,然后将ViewGroup实例传给setContentView。ViewGroup是个抽象类,一般直接使用的都是它的子类,被称之为布局管理器。
Android有两种方式编写UI界面,一种是在xml布局资源文件中,另一种是直接在代码中编写,如上面的传入一个View实例的做法就是直接在代码中编写,这是传统的Form编程的做法。现在比较推荐的是在xml布局资源文件中编写UI界面,这样一来就可以将应用表示层与逻辑层相分离,无需修改代码就可以修改表示层。
要编写复杂的UI界面,需要掌握android中常用的布局管理器。主要有:
AbsoluteLayout:绝对布局
FrameLayout:帧布局
LinearLayout:线性布局
RelativeLayout:相对布局
TableLayout:表格布局
GridLayou:网格布局(Android 4.0添加的新的布局管理器)
1.LinearLayout 线性布局
线性布局就是放在其中的View组件将进行线性对齐排列,可以设置是垂直排列还是水平排列。
新建一个布局资源文件的方法:
右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建LinearLayout布局文件,就选择LinearLayout作为其根节点即可。
linear_layout.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"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="aaaaaa" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="cccccc" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="dddddd" /> </LinearLayout>
activity中代码如下:
protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.linear_layout); }
显示效果:
常用的几个属性:
1)orientation属性:设置LinearLayout中组件的排列方式,可以取值vertical或者horizontal表示垂直排成一列或者水平排成一行。
上面代码中,如果把orientation设置为horizontal。显示则变为:
因为只显示一行,而第一个Button的宽度就是充满父元素,所以只显示出来了第一个Button。
2)layout_width属性:设置在父元素中该组件的宽度,可以取值wrap_content、match_parent或者fill_parent。其中wrap_content表示宽度能够包裹该组件中的内容即可,fill_parent和match_parent含义相同表示宽度充满父元素,现在,更常使用match_parent,而很少用fill_parent。
如上面代码中把所有的Button的layout_width都设置为wrap_content,则显示效果如下:
3)layout_height属性:设置在父元素中该组件的宽度,取值同layout_width。
4)grativity属性:设置该容器内组件的对齐方式。
如在LinearLayout节点中添加属性:android:gravity="center_vertical"
则显示效果如下:
该属性的取值可以是:top、bottom、left、right、center、center_vertical、center_horizontal等值,或者这些值相或(即位或运算 | )
如:android:gravity="bottom|right" 显示效果
5)layout_gravity属性:当前控件在父元素的位置。
如将aaaaaa那个Button中layout_gravity设置为”center”,其效果将会与其所处容器即LinearLayout中的gravity属性效果进行叠加,显示如下:
垂直上进行了居中,水平上还是排在bbbbbb的左边
6)layout_weight属性:在子控件中设置父元素中多出来的额外空间的分配权重。
此时,如果只在aaaaaa这个button中设置layout_weight属性,可以设置为任意值,习惯设置为1。则aaaaaa这个button会拉伸占据剩下的空间,显示如下:
如果同时在aaaaaa和dddddd两个button中都设置layout_weight属性,且第一个设置为1,第二个设置为2,则之前多出来的剩余空间会分给aaaaaa 1/3,分给dddddd 2/3,即各自的权重值/总的权重值,即为各自所分得的剩余空间的比例,显示如下:
7)weightSum属性:设置容器中剩余空间的总的权重值,这个属性是LinearLayout中的属性,而layout_weight是各个子控件中的属性,若不设置,则默认为各个子控件layout_weight属性值的总和。
若如上面aaaaaa的layout_weight值为1,dddddd的layout_weight的值为2,同时在LinearLayout中设置weightSum值为6,则仍会有一半的剩余空间,aaaaaa只分得原来剩余空间的1/6,dddddd分得2/6,显示如下:
8)visibility属性:控制是否显示,取值可以是invisible、visible、gone。visible表示显示出来,invisible和gone不显示出来,其中invisible不显示,但控件仍然存在,占用着空间,而gone表示控件不存在了,也就不占用空间了。
如:cccccc设置visibility属性为gone,显示如下:
若改为invisible:
LinearLayout设置invisible:
2.RelativeLayout:相对布局
顾名思义,即根据各控件的相对位置进行布局,相对位置,可以是子控件A相对父控件的位置,也可以是子控件A相对于子控件B的位置。
右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建RelativeLayout布局文件,就选择RelativeLayout作为其根节点即可。文件名为relative_layout.xml。
代码如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/aa" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="aaaaaa" /> <Button android:id="@+id/bb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/aa" android:layout_alignTop="@id/aa" android:text="bbbbbb" /> <Button android:id="@+id/cc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/aa" android:layout_alignBottom="@id/aa" android:text="cccccc" /> <Button android:id="@+id/dd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/aa" android:layout_alignLeft="@id/aa" android:text="dddddd" /> <Button android:id="@+id/ee" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/aa" android:layout_alignLeft="@id/aa" android:text="eeeeee" /> </RelativeLayout>
修改FirstActivity中setContentView(R.layout.relative_layout);
显示效果:
aaaaaa在父容器中居中显示
bbbbbb在aaaaaa的右边显示,并且与aaaaaa顶部对齐
ccccccc在aaaaaa的左边显示,并且与aaaaaa顶部对齐
dddddd在aaaaaa的上面显示,并且与aaaaaa左对齐
eeeeee在aaaaaa的下面显示,并且与aaaaaa左对齐
主要属性:均为设置父子相对位置,或者子控件与子控件的相对位置
android:layout_toRightOf 在指定控件的右边
android:layout_toLeftOf 在指定控件的左边
android:layout_above 在指定控件的上边
android:layout_below 在指定控件的下边
android:layout_alignBaseline 跟指定控件水平对齐
android:layout_alignLeft 跟指定控件左对齐
android:layout_alignRight 跟指定控件右对齐
android:layout_alignTop 跟指定控件顶部对齐
android:layout_alignBottom 跟指定控件底部对齐
android:layout_alignParentLeft 是否跟父布局左对齐
android:layout_alignParentTop 是否跟父布局顶部对齐
android:layout_alignParentRight 是否跟父布局右对齐
android:layout_alignParentBottom 是否跟父布局底部对齐
android:layout_centerVertical 在父布局中垂直居中
android:layout_centerHorizontal 在父布局中水平居中
android:layout_centerInParent 在父布局中居中
3.FrameLayout:帧布局
如同Flash或者photoshop中图层的概念,在上面的图层遮盖下面的图层,没被遮到的地方仍然显示出来。
右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建FrameLayout布局文件,就选择FrameLayout作为其根节点即可。文件名为frame_layout.xml。
代码如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:src="@drawable/bg" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <ImageView android:src="@drawable/hero" android:layout_width="wrap_content" android:layout_gravity="center" android:layout_height="wrap_content"/> </FrameLayout>
依次放置两个ImageView用于显示两张图片,第一张为背景图片,第二张为一个人物图片。
修改FirstActivity中setContentView(R.layout.frame_layout);
显示效果如下:
先添加的控件位于下面,后添加的控件位于上面。
4.AbsoluteLayout:绝对布局
根据绝对坐标位置进行布局,不灵活,故而很少使用。
eclipse中也提示:AbsoluteLayout is deprecated,即不建议使用绝对布局。
新建一个layout文件,名为absolute_layout.xml,代码入下:
<?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_x="100dp" android:layout_y="100dp" android:text="aaaaaa" /> </AbsoluteLayout>
修改FirstActivity中代码:setContentView(R.layout.absolute_layout);
显示如下:
属性:
android:layout_x 指定控件在父布局的x轴坐标
android:layout_y 指定控件在父布局的y轴坐标
5.TableLayout:表格布局
需要配合TableRow进行使用,也不是太常用。
在TableLayout中每加入一个TableRow子节点,就表示在表格中加入了一行,之后在TableRow中每加入一个控件,就表示加入了一列。注意TableRow单元行里的单元格的宽度小于默认的宽度时就不起作用,其默认是fill_parent,高度可以自定义大小。
如果直接在TableLayout中添加控件,那么该控件将会占用一行。
新建一个layout布局文件,名为table_layout.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="aaaaaa" /> <TableRow > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cccccc" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cccccc" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cccccc" /> </TableRow> <TableRow > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="dddddd" /> </TableRow> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="eeeeee" /> </TableLayout>
修改setContentView(R.layout.table_layout);
显示效果如下:
第0行一个按钮aaaaaa
第1行6个按钮,但是父控件宽度有限,只显示了5个
第2行一个TableRow,里面加了一个按钮dddddd
第3行也是一个按钮eeeeee
主要属性:
android:shrinkColumns 设置收缩的列,其值为要收缩的列的索引,从0开始,多个时用逗号分隔。
如:android:shrinkColumns="0,2",表示第0和第2列收缩,显示效果如下:
可以看出没有放在TableRow中的行没有被收缩。
android:stretchColumns 设置拉伸的列,其值为要收缩的列的索引,从0开始,多个时用逗号分隔。如上显示中第1行有6个按钮,父容器宽度不够用,此时拉伸任何一列都不会有效果。若第1行只有两个按钮,此时,设置android:stretchColumns="1",则会把第1列拉伸,充满父容器剩下的空间。显示效果如下:
android:collapseColumns 设置要隐藏的列,这里的隐藏于visibility设置为gone效果相同的。隐藏之后不占用父容器的空间。
如:android:collapseColumns="1,3,5",则第一行6个按钮,只剩下3个bbbbbb
6.GridLayout:网格布局
Android4.0中新增的布局管理器。因此,在android4.0之后的版本才可以直接使用。
新建项目设置最小SDK为14,其他也要高于14。
新建一个layout文件,名为grid_layout.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="5" android:rowCount="4"> <Button android:layout_row="0" android:text="aaaaaa" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:layout_row="1" android:layout_column="0" android:text="bbbbbb" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:layout_row="2" android:layout_column="2" android:text="cccccc" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:layout_row="3" android:layout_column="1" android:text="dddddd" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </GridLayout>
显示效果如下:
整个布局被分为4行5列。
主要属性:
android:columnCount 设置GridLayout的列数
android:rowCount设置GridLayou的行数
每个添加到GridLayout中的子控件都可以设置如下属性:
android:layout_row 设置该元素所在行,从0开始
android:layout_column 设置该元素所在列,从0开始
android:layout_rowSpan 设置该元素所跨的行数
android:layout_columnSpan 设置该元素所跨的列数。
Android中View绘制流程以及invalidate(),requsetLaytout()以及requestFocus()方法分析
整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘(draw),其框架过程如下:
步骤其实为host.layout()
接下来温习一下整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现。
流程一: mesarue()过程
主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
具体的调用链如下:
ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调
View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性: mMeasuredHeight)和宽(对应属性:mMeasureWidth) ;
2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。
2.1 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。
整个measure调用流程就是个树形的递归过程
measure函数原型为 View.java 该函数不能被重载
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //.... //回调onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); //more }
为了大家更好的理解,采用“二B程序员”的方式利用伪代码描述该measure流程
//回调View视图里的onMeasure过程 private void onMeasure(int height , int width){ //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight) //1、该方法必须在onMeasure调用,否者报异常。 setMeasuredDimension(h , l) ; //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个measure()过程就是个递归过程 //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都 measureChildWithMargins(child , h, i) ; //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下: //child.measure(h, l) } } //该方法具体实现在ViewGroup.java里 。 protected void measureChildWithMargins(View v, int height , int width){ v.measure(h,l) }
流程二、 layout布局过程:
主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
layout函数原型为 ,位于View.java
/* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴 * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */ public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴 if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局 mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; }
同样地, 将上面layout调用流程,用伪代码描述如下:
// layout()过程 ViewRoot.java // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout() private void performTraversals(){ //... View mView ; mView.layout(left,top,right,bottom) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void onLayout(int left , int top , right , bottom){ //如果该View不是ViewGroup类型 //调用setFrame()方法设置该控件的在父视图上的坐标轴 setFrame(l ,t , r ,b) ; //-------------------------- //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个layout()过程就是个递归过程 child.layout(l, t, r, b) ; } }
流程三、 draw()绘图过程
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
5、绘制滚动条
于是,整个调用链就这样递归下去了。
同样地,使用伪代码描述如下:
// draw()过程 ViewRoot.java // 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图 private void draw(){ //... View mView ; mView.draw(canvas) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void draw(Canvas canvas){ //该方法会做如下事情 //1 、绘制该View的背景 //2、为绘制渐变框做一些准备操作 //3、调用onDraw()方法绘制视图本身 //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。 // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。 //5、绘制渐变框 } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 @Override protected void dispatchDraw(Canvas canvas) { // //其实现方法类似如下: int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ View child = getChildAt(i) ; //调用drawChild完成 drawChild(child,canvas) ; } } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 protected void drawChild(View child,Canvas canvas) { // .... //简单的回调View对象的draw()方法,递归就这么产生了。 child.draw(canvas) ; //......... }
强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现measure()过程和layout()过程即可 。
这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历。
invalidate()方法 :
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。
一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。
说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制任何视图包括该调用者本身。
一般引起invalidate()操作的函数如下:
1、setVisibility()方法:
当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
requestFocus()函数说明:
说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。
下面写个简单的小Demo吧,主要目的是给大家演示绘图的过程以及每个流程里该做的一些功能。截图如下:
1、 MyViewGroup.java 自定义ViewGroup类型
/** * @author http://http://blog.csdn.net/qinjuning */ //自定义ViewGroup 对象 public class MyViewGroup extends ViewGroup{ private static String TAG = "MyViewGroup" ; private Context mContext ; public MyViewGroup(Context context) { super(context); mContext = context ; init() ; } //xml定义的属性,需要该构造函数 public MyViewGroup(Context context , AttributeSet attrs){ super(context,attrs) ; mContext = context ; init() ; } //为MyViewGroup添加三个子View private void init(){ //调用ViewGroup父类addView()方法添加子View //child 对象一 : Button Button btn= new Button(mContext) ; btn.setText("I am Button") ; this.addView(btn) ; //child 对象二 : ImageView ImageView img = new ImageView(mContext) ; img.setBackgroundResource(R.drawable.icon) ; this.addView(img) ; //child 对象三 : TextView TextView txt = new TextView(mContext) ; txt.setText("Only Text") ; this.addView(txt) ; //child 对象四 : 自定义View MyView myView = new MyView(mContext) ; this.addView(myView) ; } @Override //对每个子View进行measure():设置每子View的大小,即实际宽和高 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ //通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView int childCount = getChildCount() ; Log.i(TAG, "the size of this ViewGroup is ----> " + childCount) ; Log.i(TAG, "**** onMeasure start *****") ; //获取该ViewGroup的实际长和宽 涉及到MeasureSpec类的使用 int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec) ; int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec) ; Log.i(TAG, "**** specSize_Widht " + specSize_Widht+ " * specSize_Heigth *****" + specSize_Heigth) ; //设置本ViewGroup的宽高 setMeasuredDimension(specSize_Widht , specSize_Heigth) ; for(int i=0 ;i<childCount ; i++){ View child = getChildAt(i) ; //获得每个对象的引用 child.measure(50, 50) ; //简单的设置每个子View对象的宽高为 50px , 50px //或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法 //this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ; } } @Override //对每个子View视图进行布局 protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub //通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView int childCount = getChildCount() ; int startLeft = 0 ;//设置每个子View的起始横坐标 int startTop = 10 ; //每个子View距离父视图的位置 , 简单设置为10px吧 。 可以理解为 android:margin=10px ; Log.i(TAG, "**** onLayout start ****") ; for(int i=0 ;i<childCount ; i++){ View child = getChildAt(i) ; //获得每个对象的引用 child.layout(startLeft, startTop, startLeft+child.getMeasuredWidth(), startTop+child.getMeasuredHeight()) ; startLeft =startLeft+child.getMeasuredWidth() + 10; //校准startLeft值,View之间的间距设为10px ; Log.i(TAG, "**** onLayout startLeft ****" +startLeft) ; } } //绘图过程Android已经为我们封装好了 ,这儿只为了观察方法调用程 protected void dispatchDraw(Canvas canvas){ Log.i(TAG, "**** dispatchDraw start ****") ; super.dispatchDraw(canvas) ; } protected boolean drawChild(Canvas canvas , View child, long drawingTime){ Log.i(TAG, "**** drawChild start ****") ; return super.drawChild(canvas, child, drawingTime) ; } }
2、MyView.java 自定义View类型,重写onDraw()方法 ,
//自定义View对象 public class MyView extends View{ private Paint paint = new Paint() ; public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } public MyView(Context context , AttributeSet attrs){ super(context,attrs); } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ //设置该View大小为 80 80 setMeasuredDimension(50 , 50) ; } //存在canvas对象,即存在默认的显示区域 @Override public void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); Log.i("MyViewGroup", "MyView is onDraw ") ; //加粗 paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); paint.setColor(Color.RED); canvas.drawColor(Color.BLUE) ; canvas.drawRect(0, 0, 30, 30, paint); canvas.drawText("MyView", 10, 40, paint); } }
主Activity只是显示了该xml文件,在此也不罗嗦了。 大家可以查看该ViewGroup的Log仔细分析下View的绘制流程以及相关方法的使用。第一次启动后捕获的Log如下,网上找了些资料,第一次View树绘制过程会走几遍,具体原因可能是某些View 发生了改变,请求重新绘制,但这根本不影响我们的界面显示效果 。
总的来说: 整个绘制过程还是十分十分复杂地,每个具体方法的实现都是我辈难以立即的,感到悲剧啊。对Android提供的一些ViewGroup对象,比如LinearLayout、RelativeLayout布局对象的实现也很有压力。 本文重在介绍整个View树的绘制流程,希望大家在此基础上,多接触源代码进行更深入地扩展。
Android中将布局文件/View添加至窗口过程分析
本文主要内容是讲解一个视图View或者一个ViewGroup对象是如何添加至应用程序窗口中的。
下文中提到的窗口可泛指我们能看到的界面,包括一个Activity呈现的界面(我们可以将之理解为应用程序窗口),一个Dialog,一个Toast,一个Menu菜单等。
首先对相关类的作用进行一下简单介绍:
Window 类 位于 /frameworks/base/core/java/android/view/Window.java
说明:该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。
源文件(部分)如下:
public abstract class Window { //... //指定Activity窗口的风格类型 public static final int FEATURE_NO_TITLE = 1; public static final int FEATURE_INDETERMINATE_PROGRESS = 5; //设置布局文件 public abstract void setContentView(int layoutResID); public abstract void setContentView(View view); //请求指定Activity窗口的风格类型 public boolean requestFeature(int featureId) { final int flag = 1<<featureId; mFeatures |= flag; mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag; return (mFeatures&flag) != 0; } //... }
PhoneWindow类 位于/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java
说明: 该类继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。并且,该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。
源文件(部分)如下:
public class PhoneWindow extends Window implements MenuBuilder.Callback { //... // This is the top-level view of the window, containing the window decor. private DecorView mDecor; //该对象是所有应用窗口的根视图 , 是FrameLayout的子类 //该对象是Activity布局文件的父视图,一般来说是一个FrameLayout型的ViewGroup // 同时也是DecorView对象的一个子视图 // This is the view in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go. private ViewGroup mContentParent; //设置标题 @Override public void setTitle(CharSequence title) { if (mTitleView != null) { mTitleView.setText(title); } mTitle = title; } //设置背景图片 @Override public final void setBackgroundDrawable(Drawable drawable) { if (drawable != mBackgroundDrawable || mBackgroundResource != 0) { mBackgroundResource = 0; mBackgroundDrawable = drawable; if (mDecor != null) { mDecor.setWindowBackground(drawable); } } } //... }
DecorView类 该类是PhoneWindow类的内部类
说明: 该类是一个FrameLayout的子类,并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏),以及TitleBar上的滚动条等 。最重要的一点是,它是所有应用窗口的根View 。
如下所示 :
源文件(部分)如下:
private final class DecorView extends FrameLayout { //... //触摸事件处理 @Override public boolean onTouchEvent(MotionEvent event) { return onInterceptTouchEvent(event); } //... }
打个不恰当比喻吧,Window类相当于一幅画(抽象概念,什么画我们未知) ,PhoneWindow为一副齐白石先生的山水画 (具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。
DecorView呈现在PhoneWindow上。
当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包括Activity对象和Window对象信息)后,就会回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面,整个调用链由此铺垫开来。setContentView()的三个构造方法调用流程本质上是一样的,我们就分析setContentView(intresId)方法。
Step 1 、Activity.setContentView(intresId) 该方法在Activity类中
该方法只是简单的回调Window对象,具体为PhoneWindow对象的setContentView()方法实现 。
public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); } public Window getWindow() { return mWindow; //Window对象,本质上是一个PhoneWindow对象 }
Step 2 、PhoneWindow.setContentView() 该方法在PhoneWindow类中
@Override public void setContentView(int layoutResID) { //是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空 if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } }
方法根据首先判断是否已经由setContentView()了获取mContentParent即View对象, 即是否是第一次调用该PhoneWindow对象setContentView()方法。如果是第一次调用,则调用installDecor()方法,否则,移除该mContentParent内所有的所有子View。最后将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。
PS:因此,在应用程序里,我们可以多次调用setContentView()来显示我们的界面。
Step 3、 PhoneWindow. installDecor() 该方法在PhoneWindow类中
private void installDecor() { if (mDecor == null) { //mDecor为空,则创建一个Decor对象 mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); } if (mContentParent == null) { //generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件 //并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent mContentParent = generateLayout(mDecor); //... }
首先、该方法首先判断mDecor对象是否为空,如果不为空,则调用generateDecor()创建一个DecorView(该类是FrameLayout子类,即一个ViewGroup视图) ;
generateDecor()方法原型为:
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }
其次、继续判断mContentParent对象是否为空,如果不为空,则调用generateLayout()方法去创建mContentParent对象。
generateLayout()方法如下:
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. //...1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值 //2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件 int layoutResource; //窗口修饰布局文件 int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { layoutResource = com.android.internal.R.layout.dialog_title_icons; } else { layoutResource = com.android.internal.R.layout.screen_title_icons; } // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = com.android.internal.R.layout.screen_progress; // System.out.println("Progress!"); } //... //3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { ProgressBar progress = getCircularProgressBar(false); if (progress != null) { progress.setIndeterminate(true); } } //... return contentParent; }
该方法会做如下事情:
1、根据窗口的风格修饰类型为该窗口选择不同的窗口布局文件(根视图)。这些窗口修饰布局文件指定一个用来存放 Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id="@android:id/content"。
例如窗口修饰类型包括FullScreen(全屏)、NoTitleBar(不含标题栏)等。选定窗口修饰类型有两种:
①、指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值;
②、为我们的Activity配置相应属性,即android:theme=“”,PhoneWindow对象调用getWindowStyle()方法
获取值。
举例如下,隐藏标题栏有如下方法:requestWindowFeature(Window.FEATURE_NO_TITLE);
或者 为Activity配置xml属性:android:theme=”@android:style/Theme.NoTitleBar”。
PS:因此,在Activity中必须在setContentView之前调用requestFeature()方法。
确定好窗口风格之后,选定该风格对应的布局文件,这些布局文件位于 frameworks/base/core/res/layout/ ,典型的窗口布局文件有:
R.layout.dialog_titile_icons R.layout.screen_title_icons
R.layout.screen_progress R.layout.dialog_custom_title
R.layout.dialog_title
R.layout.screen_title // 最常用的Activity窗口修饰布局文件
R.layout.screen_simple //全屏的Activity窗口布局文件
分析Activity最常用的一种窗口布局文件,R.layout.screen_title :
<!-- This is an optimized layout for a screen, with the minimum set of features enabled. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <FrameLayout android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle"> <TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
该布局文件很简单,一个LinearLayout下包含了两个子FrameLayout视图,第一个FrameLayout用来显示标题栏(TitleBar),该TextView 视图id为title(android:id="@android:id/title");第二个FrameLayout用来显示我们Activity的布局文件的父视图,该FrameLayoutid为content(android:id="@android:id/content") 。
全屏的窗口布局文件 R.layout.screen_simple:
<--This is an optimized layout for a screen, with the minimum set of features enabled. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/content" android:fitsSystemWindows="true" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" />
该布局文件只有一个FrameLayout,用来显示我们Activity的布局文件,该FrameLayoutid为
android:id="@android:id/content"
2、前面一步我们确定窗口修饰布局文件后,mDecor做为根视图将该窗口布局对应的视图添加进去,并且获取id为content的View,将其赋值给mContentParent对象,即我们前面中提到的第二个FrameLayout。
At Last、产生了mDecor和mContentParent对象后,就将我们的Activity布局文件直接添加至mContentParent父视图中即可。
我们再次回到 Step 2 中PhoneWindow.setContentView() 该方法在PhoneWindow类中
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } }
整个过程主要是如何把Activity的布局文件添加至窗口里,上面的过程可以概括为:
1、创建一个DecorView对象,该对象将作为整个应用窗口的根视图
2、创建不同的窗口修饰布局文件,并且获取Activity的布局文件该存放的地方,由该窗口修饰布局文件内id为content的FrameLayout指定 。
3、将Activity的布局文件添加至id为content的FrameLayout内。
最后,当AMS(ActivityManagerService)准备resume一个Activity时,会回调该Activity的handleResumeActivity()方法,
该方法会调用Activity的makeVisible方法 ,显示我们刚才创建的mDecor 视图族。
//系统resume一个Activity时,调用此方法 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { ActivityRecord r = performResumeActivity(token, clearHide); //... if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } }
handleResumeActivity()方法原型如下: 位于ActivityThread类中
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); // 获取WindowManager对象 wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); //使其处于显示状况 }
接下来就是,如何把我们已经创建好的窗口通知给WindowManagerService ,以便它能够把这个窗口显示在屏幕上。
<pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <div style="padding-top:20px"> <p style="font-size:12px;">版权声明:本文为博主原创文章,未经博主允许不得转载。</p> </div>
Android的系统是linux系统,我们可以使用linux命令mouunt来获取linux的挂载目录。
使用命令获取到的目录我并没有遍历,如果你还是获取不到,可以把mount获去到的所有目录都遍历一次。
File sdcard ; @SuppressLint("SdCardPath") public File getSdCardFile() { if (sdcard != null) { return sdcard; } List<String> list = getExtSDCardPath(); boolean isRun = true; if (list.size() > 0) { sdcard = new File(list.get(list.size() - 1)); if (sdcard.isDirectory()) { if (sdcard.getFreeSpace() == 0) { isRun = true; } else { isRun = false; } } } if (isRun) { sdcard = Environment.getExternalStorageDirectory(); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { sdcard = new File("/sdcard/"); log.e("sdcard spance:" + sdcard.getFreeSpace()); if (sdcard.getFreeSpace() == 0) { sdcard = new File("/sdcard1/"); log.e("sdcard1 space:" + sdcard.getFreeSpace()); } if (sdcard.getFreeSpace() == 0) { sdcard = new File("/sdcard2/"); log.e("sdcard2 space:" + sdcard.getFreeSpace()); } } } log.e("data:" + sdcard.getAbsolutePath()); return sdcard; } public List<String> getExtSDCardPath() { List<String> lResult = new ArrayList<String>(); try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("mount"); InputStream is = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null) { if (line.contains("extSdCard")) { String[] arr = line.split(" "); String path = arr[1]; if (path.lastIndexOf("extSdCard") == path.length() - 9) { File file = new File(path); if (file.isDirectory()) { lResult.add(path); } } } else if (line.contains("/sdcard")) { String[] arr = line.split(" "); String path = arr[1]; if (path.lastIndexOf("/sdcard") == path.length() - 6) { File file = new File(path); if (file.isDirectory()) { lResult.add(path); } } else { String number = path.substring(path.lastIndexOf("/sdcard") + 7); try { Integer.parseInt(number); File file = new File(path); if (file.isDirectory()) { lResult.add(path); } } catch (Exception e) { } } } } isr.close(); } catch (Exception e) { } return lResult; }
不过,首选获取存储还是先使用Android提供的方法,判断并获取Sdcard目录。
//判断sdcard是否存在 Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); //如果存在,获取存储File目录 Environment.getExternalStorageDirectory();
Android SD卡路径问题以及如何获取SDCard 内存
在研究拍照后突破的存储路径的问题,开始存储路径写死为: private String folder = "/sdcard/DCIM/Camera/"(SD卡上拍照程序的图片存储路径); 后来发现这样写虽然一般不会出错,但不是很好,因为不同相机,可能路径会出问题。较好的方法是通过Environment 来获取路径,最后给出一个例子,教你怎样获取SDCard 的内存,显示出来告诉用户。讲述的内容如下:
0、获取sd卡路径。
1、讲述 Environment 类。
2、讲述 StatFs 类。
3、完整例子读取 SDCard 内存
0、获取sd卡路径
方法一: private String folder = "/sdcard/DCIM/Camera/"(SD卡上拍照程序的图片存储路径); //写死绝对路径,不赞成使用
方法二:
public String getSDPath(){ File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState() .equals(Android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在 if (sdCardExist) { sdDir = Environment.getExternalStorageDirectory();//获取跟目录 } return sdDir.toString(); }
然后:在后面加上斜杠,在加上文件名
String fileName = getSDPath() +"/" + name;//以name存在目录中
1、讲述 Environment 类
Environment 是一个提供访问环境变量的类。
Environment 包含常量:
MEDIA_BAD_REMOVAL
解释:返回getExternalStorageState() ,表明SDCard 被卸载前己被移除
MEDIA_CHECKING
解释:返回getExternalStorageState() ,表明对象正在磁盘检查。
MEDIA_MOUNTED
解释:返回getExternalStorageState() ,表明对象是否存在并具有读/写权限
MEDIA_MOUNTED_READ_ONLY
解释:返回getExternalStorageState() ,表明对象权限为只读
MEDIA_NOFS
解释:返回getExternalStorageState() ,表明对象为空白或正在使用不受支持的文件系统。
MEDIA_REMOVED
解释:返回getExternalStorageState() ,如果不存在 SDCard 返回
MEDIA_SHARED
解释:返回getExternalStorageState() ,如果 SDCard 未安装 ,并通过 USB 大容量存储共享 返回
MEDIA_UNMOUNTABLE
解释:返回getExternalStorageState() ,返回 SDCard 不可被安装 如果 SDCard 是存在但不可以被安装
MEDIA_UNMOUNTED
解释:返回getExternalStorageState() ,返回 SDCard 已卸掉如果 SDCard 是存在但是没有被安装
Environment 常用方法:
方法:getDataDirectory()
解释:返回 File ,获取 Android 数据目录。
方法:getDownloadCacheDirectory()
解释:返回 File ,获取 Android 下载/缓存内容目录。
方法:getExternalStorageDirectory()
解释:返回 File ,获取外部存储目录即 SDCard
方法:getExternalStoragePublicDirectory(String type)
解释:返回 File ,取一个高端的公用的外部存储器目录来摆放某些类型的文件
方法:getExternalStorageState()
解释:返回 File ,获取外部存储设备的当前状态
方法:getRootDirectory()
解释:返回 File ,获取 Android 的根目录
2、讲述 StatFs 类
StatFs 一个模拟linux的df命令的一个类,获得SD卡和手机内存的使用情况
StatFs 常用方法:
getAvailableBlocks()
解释:返回 Int ,获取当前可用的存储空间
getBlockCount()
解释:返回 Int ,获取该区域可用的文件系统数
getBlockSize()
解释:返回 Int ,大小,以字节为单位,一个文件系统
getFreeBlocks()
解释:返回 Int ,该块区域剩余的空间
restat(String path)
解释:执行一个由该对象所引用的文件系统
3、完整例子读取 SDCard 内存
存储卡在 Android 手机上是可以随时插拔的,每次的动作都对引起操作系统进行 ACTION_BROADCAST,本例子将使用上面学到的方法,计算出 SDCard 的剩余容量和总容量。代码如下:
package com.terry; import java.io.File; import java.text.DecimalFormat; import android.R.integer; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.StatFs; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class getStorageActivity extends Activity { private Button myButton; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findView(); viewHolder.myButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub getSize(); } }); } void findView(){ viewHolder.myButton=(Button)findViewById(R.id.Button01); viewHolder.myBar=(ProgressBar)findViewById(R.id.myProgressBar); viewHolder.myTextView=(TextView)findViewById(R.id.myTextView); } void getSize(){ viewHolder.myTextView.setText(""); viewHolder.myBar.setProgress(0); //判断是否有插入存储卡 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File path =Environment.getExternalStorageDirectory(); //取得sdcard文件路径 StatFs statfs=new StatFs(path.getPath()); //获取block的SIZE long blocSize=statfs.getBlockSize(); //获取BLOCK数量 long totalBlocks=statfs.getBlockCount(); //己使用的Block的数量 long availaBlock=statfs.getAvailableBlocks(); String[] total=filesize(totalBlocks*blocSize); String[] availale=filesize(availaBlock*blocSize); //设置进度条的最大值 int maxValue=Integer.parseInt(availale[0]) *viewHolder.myBar.getMax()/Integer.parseInt(total[0]); viewHolder.myBar.setProgress(maxValue); String Text="总共:"+total[0]+total[1]+"\n" +"可用:"+availale[0]+availale[1]; viewHolder.myTextView.setText(Text); }else if(Environment.getExternalStorageState().equals(Environment.MEDIA_REMOVED)){ Toast.makeText(getStorageActivity.this, "没有sdCard", 1000).show(); } } //返回数组,下标1代表大小,下标2代表单位 KB/MB String[] filesize(long size){ String str=""; if(size>=1024){ str="KB"; size/=1024; if(size>=1024){ str="MB"; size/=1024; } } DecimalFormat formatter=new DecimalFormat(); formatter.setGroupingSize(3); String result[] =new String[2]; result[0]=formatter.format(size); result[1]=str; return result; } }
Android中被使用的消息队列的代码在目录\sources\android-22\android\os下,主要涉及到以下几个类文件
Handler.java 在这里面代表一个消息实体对象
Looper.java 主要用来监听MessageQueue的消息,他存放于ThreadLocal中
Message.java 主要用来处理消息的发送,以及响应消息的业务处理
MessageQueue.java 是一个单独的线程,可与Looper整合,实现一个完全独立的消息处理机制
Message.java public final class Message implements Parcelable { public int what; //消息种类 public int arg1; //低开销的整型参数 public int arg2; public Object obj; //Object型数据 public Messenger replyTo; //消息处理完后通知给发送者 /*package*/ int flags; //消息标记:正在使用和异步等 /*package*/ long when; //消息创建时的时间 /*package*/ Bundle data; //消息附带的额外数据 /*package*/ Handler target; //消息接受者,处理者 /*package*/ Runnable callback; //优先使用回调处理来处理消息 /*package*/ Message next; //下一个消息,形成链表 private static Message sPool; //消息池中的头消息
上面中的target,通常由重新实现的Handler子类的handleMessage函数来处理消息
public static Message obtain() { //获取消息的函数,如果有消息的话则获取出来m,链表指针移动一位,否则则返回一条空消息 synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } public void sendToTarget() { //发送消息给处理者 target.sendMessage(this); //调用Handler.java中的函数 } }
MessageQueue.java public final class MessageQueue { Message mMessages; //当前要处理的消息 //当需要从链表中获取一个消息时,就会调用next函数,若消息队列中没有消息,则会阻塞等待,通过调用nativePollOnce函数来完成 Message next() {...} boolean enqueueMessage(Message msg, long when) { //按时间顺序添加消息 if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); //调用底层唤醒函数,管道唤醒 } } return true; }
Looper.java public final class Looper { final MessageQueue mQueue; //消息队列 final Thread mThread; //Looper联系的线程 public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //先会检查是否有Looper,若有则抛出异常,没有的话则创建一个Looper实例保存起来 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } //在这个线程中运行消息队列,调用quit()停止 public static void loop() { ... final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // 从消息队列中取出一条消息 if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); //交给msg的handler分发消息处理 ... } //取出当前线程的Looper,返回空则表示当前线程没有Looper public static Looper myLooper() { return sThreadLocal.get(); } }
Handler.java public class Handler { //定义Callback接口 public interface Callback { public boolean handleMessage(Message msg); } //子类要实现的消息处理方法 public void handleMessage(Message msg) { } * Handle system messages here. */ public void dispatchMessage(Message msg) { //分发信息 if (msg.callback != null) { //若指定了msg.callback,则由它处理 handleCallback(msg); } else { if (mCallback != null) { //若指定了Handler.mCallback,则由它处理 if (mCallback.handleMessage(msg)) { //调用mCallback接口的实现方法 return; } } handleMessage(msg); 最后才调用Handler自身重载的handleMessage方法 } } 分发消息函数中,消息先会检查自身有没有处理自身的回调Runnable,若有则由它处理,若没有则会检查该handler有无自身的回调处理,若有则调用,若没有则调用自身重载的handleMessage方法 //Handler的生成总是和它当前所处线程有关的,如果当前线程中没有一个Looper,则会报错,UI线程中默认有产生Looper的函数 public Handler() { this(null, false); } //使用指定的Looper(可以处理那个Looper线程中的消息),不用默认的从当前线程中取出Looper public Handler(Looper looper) { this(looper, null, false); } ... }
相关文章
- 下面本文章来给大家介绍在php中成员变量的一些对比了,文章举了四个例子在这例子中分别对不同成员变量进行测试与获取操作,下面一起来看看。 有如下4个代码示例,你认...2016-11-25
- php 获取用户IP与IE信息程序 function onlineip() { global $_SERVER; if(getenv('HTTP_CLIENT_IP')) { $onlineip = getenv('HTTP_CLIENT_IP');...2016-11-25
- php获取一个文件夹的mtime的程序了,这个就是时间问题了,对于这个问题我们来看小编整理的几个例子,具体的操作例子如下所示。 php很容易获取到一个文件夹的mtime,可以...2016-11-25
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
- 获取网站icon,常用最简单的方法就是通过website/favicon.ico来获取,不过由于很多网站都是在页面里面设置favicon,所以此方法很多情况都不可用。 更好的办法是通过google提供的服务来实现:http://www.google.com/s2/favi...2014-06-07
- 主要功能:获取浏览器显示区域(可视区域)的高度 : $(window).height(); 获取浏览器显示区域(可视区域)的宽度 :$(window).width(); 获取页面的文档高度 $(document).height(); 获取页面的文档宽度 :$(document).width();...2015-10-21
Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- jquery中jquery.offset().top / left用于获取div距离窗口的距离,jquery.position().top / left 用于获取距离父级div的距离(必须是绝对定位的div)。 (1)先介绍jquery.offset().top / left css: 复制代码 代码如下: *{ mar...2013-10-13
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
- 1、先讲讲JQuery的概念,JQuery首先是由一个 America 的叫什么 John Resig的人创建的,后来又很多的JS高手也加入了这个团队。其实 JQuery是一个JavaScript的类库,这个类库集合了很多功能方法,利用类库你可以用简单的一些代...2014-05-31
- 如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
- 深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
- 这篇文章主要介绍了C#获取字符串后几位数的方法,实例分析了C#操作字符串的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
android.os.BinderProxy cannot be cast to com解决办法
本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20- 复制代码 代码如下:$nodes = @$xpath->query("//*[@id='main_pr']/img/@src");$prurl = $nodes->item(0)->nodeValue;...2013-10-04