yinge00 2012-03-09
1.android屏幕分辨率与load资源文件的问题:
举个例子:
在320X480的屏幕下,load一个300X150的图片
开始为了图省事,将图片资源都放在drawable-hdpi目录下,结果发现通过BitmapFactory.decodeResource函数load进来的Bitmap变成尺寸200X100了,缩小了1/3,然后就将图片放在drawable-mdpi目录下,load后的图片尺寸是正常的。
总结:看来应该是为了自适应屏幕,android将小屏幕的资源在大屏幕上load时,会缩小。
2.Android屏幕分辨率详解(VGA、HVGA、QVGA、WVGA、WQVGA)
这些术语都是指屏幕的分辨率。
VGA:VideoGraphicsArray,即:显示绘图矩阵,相当于640×480像素;
HVGA:Half-sizeVGA;即:VGA的一半,分辨率为480×320;
QVGA:QuarterVGA;即:VGA的四分之一,分辨率为320×240;
WVGA:WideVideoGraphicsArray;即:扩大的VGA,分辨率为800×480像素;
WQVGA:WideQuarterVGA;即:扩大的QVGA,分辨率比QVGA高,比VGA低,一般是:400×240,480×272
Drawable(hdpi,ldpi,mdpi)的区别:
主要是为了支持多分辨率的.
hdpi里面主要放高分辨率的图片,如WVGA(480×800),FWVGA(480×854)
mdpi里面主要放中等分辨率的图片,如HVGA(320×480)
ldpi里面主要放低分辨率的图片,如QVGA(240×320)
系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片
所以在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片。
总结:结合第1个注意事项自己总结吧!
3.Window
activity拥有一个或多个window(activity有一个Window成员,如果还是用popup对话框的话,则会拥有多个window)
一个activity的defaultwindow对应一个PhoneWindow,PhoneWindow中有过一个DecorView,它是所有view的根。
一个activity可以拥有一个或者多个window(如使用popupdialog)
另外,android支持surfaceView,这样一个window就可以拥有多个surface了
window->viewhierachy(DecorView是tree的root)->ViewRoot->Surface
某一个view->surface
surfaceview是在viewhierachy中embedded的surface
surfaceView类似于symbian中的DSA,直接访问surface
普通的是通过view访问surface
windowmanager会通过layer协调各个surface画图到framebuffer中去
一个activity可以拥有一个或者多个window(如使用popupdialog)
另外,android支持surfaceView,这样一个window就可以拥有多个surface了
window->viewhierachy(DecorView是tree的root)->ViewRoot->Surface
某一个view->surface
surfaceview是在viewhierachy中embedded的surface
surfaceView类似于symbian中的DSA,直接访问surface
普通的是通过view访问surface
windowmanager会通过layer协调各个surface画图到framebuffer中去
4:Canvas
android绘图最终绘制到surface上,它由windowmanager维护。
canvas相当于surface的设备环境,它提供了绘制的方法,并且指向surface
canvas是ViewRoot向windowmanager请求获取的,ViewRoot是客户端窗口(decorview及其childview)与WindowManager(surface)的桥梁,ViewRoot存在于windowmanagerproxy中。
所有客户端view被动接受ViewRoot传递过来的canvas进行绘图
5.onInterceptTouchEvent和onTouchEvent调用时序
onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截,Android这么设计的想法也很好理解,由于ViewGroup会包含若干childView,因此需要能够统一监控各种touch事件的机会,因此纯粹的不能包含子view的控件是没有这个方法的,如LinearLayout就有,TextView就没有。
onInterceptTouchEvent()使用也很简单,如果在ViewGroup里覆写了该方法,那么就可以对各种touch事件加以拦截。但是如何拦截,是否所有的touch事件都需要拦截则是比较复杂的,touch事件在onInterceptTouchEvent()和onTouchEvent以及各个childView间的传递机制完全取决于onInterceptTouchEvent()和onTouchEvent()的返回值。并且,针对down事件处理的返回值直接影响到后续move和up事件的接收和传递。
关于返回值的问题,基本规则很清楚,如果returntrue,那么表示该方法消费了此次事件,如果returnfalse,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理。
SDK给出的说明如下:
·Youwillreceivethedowneventhere.
·Thedowneventwillbehandledeitherbyachildofthisviewgroup,orgiventoyourownonTouchEvent()methodtohandle;thismeansyoushouldimplementonTouchEvent()toreturntrue,soyouwillcontinuetoseetherestofthegesture(insteadoflookingforaparentviewtohandleit).Also,byreturningtruefromonTouchEvent(),youwillnotreceiveanyfollowingeventsinonInterceptTouchEvent()andalltouchprocessingmusthappeninonTouchEvent()likenormal.
·Foraslongasyoureturnfalsefromthisfunction,eachfollowingevent(uptoandincludingthefinalup)willbedeliveredfirsthereandthentothetarget'sonTouchEvent().
·Ifyoureturntruefromhere,youwillnotreceiveanyfollowingevents:thetargetviewwillreceivethesameeventbutwiththeactionACTION_CANCEL,andallfurthereventswillbedeliveredtoyouronTouchEvent()methodandnolongerappearhere.
由于onInterceptTouchEvent()的机制比较复杂,上面的说明写的也比较复杂,总结一下,基本的规则是:
1.down事件首先会传递到onInterceptTouchEvent()方法
2.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后returnfalse,那么后续的move,up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
3.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后returntrue,那么后续的move,up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
4.如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
5.如果最终需要处理事件的view的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
5.onTouchEvent
onTouchEvent中要处理的最常用的3个事件就是:ACTION_DOWN、ACTION_MOVE、ACTION_UP。
这三个事件标识出了最基本的用户触摸屏幕的操作,含义也很清楚。虽然大家天天都在用它们,但是有一点请留意,ACTION_DOWN事件作为起始事件,它的重要性是要超过ACTION_MOVE和ACTION_UP的,如果发生了ACTION_MOVE或者ACTION_UP,那么一定曾经发生了ACTION_DOWN。
从Android的源代码中能看到基于这种不同重要性的理解而实现的一些交互机制,SDK中也有明确的提及,例如在ViewGroup的onInterceptTouchEvent方法中,如果在ACTION_DOWN事件中返回了true,那么后续的事件将直接发给onTouchEvent,而不是继续发给onInterceptTouchEvent。
6.onClick、onLongClick与onTouchEvent
曾经看过一篇帖子提到,如果在View中处理了onTouchEvent,那么就不用再处理onClick了,因为Android只会触发其中一个方法。这个理解是不太正确的,针对某个view,用户完成了一次触碰操作,显然从传感器上得到的信号是手指按下和抬起两个操作,我们可以理解为一次Click,也可以理解为发生了一次ACTION_DOWN和ACTION_UP,那么Android是如何理解和处理的呢?
在Android中,onClick、onLongClick的触发是和ACTION_DOWN及ACTION_UP相关的,在时序上,如果我们在一个View中同时覆写了onClick、onLongClick及onTouchEvent的话,onTouchEvent是最先捕捉到ACTION_DOWN和ACTION_UP事件的,其次才可能触发onClick或者onLongClick。主要的逻辑在View.java中的onTouchEvent方法中实现的:
caseMotionEvent.ACTION_DOWN:
mPrivateFlags|=PRESSED;
refreshDrawableState();
if((mViewFlags&LONG_CLICKABLE)==LONG_CLICKABLE){
postCheckForLongClick();
}
break;
caseMotionEvent.ACTION_UP:
if((mPrivateFlags&PRESSED)!=0){
booleanfocusTaken=false;
if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
focusTaken=requestFocus();
}
if(!mHasPerformedLongPress){
if(mPendingCheckForLongPress!=null){
removeCallbacks(mPendingCheckForLongPress);
}
if(!focusTaken){
performClick();
}
}
…
break;
可以看到,Click的触发是在系统捕捉到ACTION_UP后发生并由performClick()执行的,performClick里会调用先前注册的监听器的onClick()方法:
publicbooleanperformClick(){
…
if(mOnClickListener!=null){
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
returntrue;
}
returnfalse;
}
LongClick的触发则是从ACTION_DOWN开始,由postCheckForLongClick()方法完成:
privatevoidpostCheckForLongClick(){
mHasPerformedLongPress=false;
if(mPendingCheckForLongPress==null){
mPendingCheckForLongPress=newCheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,ViewConfiguration.getLongPressTimeout());
}
可以看到,在ACTION_DOWN事件被捕捉后,系统会开始触发一个postDelayed操作,delay的时间在Eclair2.1上为500ms,500ms后会触发CheckForLongPress线程的执行:
classCheckForLongPressimplementsRunnable{
…
publicvoidrun(){
if(isPressed()&&(mParent!=null)
&&mOriginalWindowAttachCount==mWindowAttachCount){
if(performLongClick()){
mHasPerformedLongPress=true;
}
}
}
…
}
如果各种条件都满足,那么在CheckForLongPress中执行performLongClick(),在这个方法中将调用onLongClick():
publicbooleanperformLongClick(){
…
if(mOnLongClickListener!=null){
handled=mOnLongClickListener.onLongClick(View.this);
}
…
}
从实现中可以看到onClick()和onLongClick()方法是由ACTION_DOWN和ACTION_UP事件捕捉后根据各种情况最终确定是否触发的,也就是说如果我们在一个Activity或者View中同时监听或者覆写了onClick(),onLongClick()和onTouchEvent()方法,并不意味着只会发生其中一种。
下面是一个onClick被触发的基本时序的Log:
04-0505:57:47.123:DEBUG/TSActivity(209):onTouchACTION_DOWN
04-0505:57:47.263:DEBUG/TSActivity(209):onTouchACTION_UP
04-0505:57:47.323:DEBUG/TSActivity(209):onClick
可以看出是按ACTION_DOWN->ACTION_UP->onClick的次序发生的。
下面是一个onLongClick被触发的基本时序的Log:
04-0506:00:04.133:DEBUG/TSActivity(248):onTouchACTION_DOWN
04-0506:00:04.642:DEBUG/TSActivity(248):onLongClick
04-0506:00:05.083:DEBUG/TSActivity(248):onTouchACTION_UP
可以看到,在保持按下的状态一定时间后会触发onLongClick,之后抬起手才会发生ACTION_UP。
7.onClick和onLongClick能同时发生吗?
要弄清楚这个问题只要理解Android对事件处理的所谓消费(consume)概念即可,一个用户的操作会被传递到不同的View控件和同一个控件的不同监听方法处理,任何一个接收并处理了该次事件的方法如果在处理完后返回了true,那么该次event就算被完全处理了,其他的View或者监听方法就不会再有机会处理该event了。
onLongClick的发生是由单独的线程完成的,并且在ACTION_UP之前,而onClick的发生是在ACTION_UP后,因此同一次用户touch操作就有可能既发生onLongClick又发生onClick。这样是不是不可思议?所以及时向系统表示“我已经完全处理(消费)了用户的此次操作”,是很重要的事情。例如,我们如果在onLongClick()方法的最后returntrue,那么onClick事件就没有机会被触发了。
下面的Log是在onLongClick()方法returnfalse的情况下,一次触碰操作的基本时序:
04-0506:00:53.023:DEBUG/TSActivity(277):onTouchACTION_DOWN
04-0506:00:53.533:DEBUG/TSActivity(277):onLongClick
04-0506:00:55.603:DEBUG/TSActivity(277):onTouchACTION_UP
04-0506:00:55.663:DEBUG/TSActivity(277):onClick
可以看到,在ACTION_UP后仍然触发了onClick()方法。
8.TextView加入链接的所有方法
1:使用android:autoLink="all"只需在textview中加入这个属性在里面写的文字中包含网址、电话、email的会自动加入连接地址。
如:
<TextViewxmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text1"android:layout_width="match_parent"
android:layout_height="match_parent"android:autoLink="all"
android:text="@string/link_text_auto"/>
2:usesastringresourcecontainingexplicit<a>tagstospecify
links.
如:<stringname="link_text_manual"><b>text2:</b>Thisissomeother
text,witha<ahref="http://www.google.com">link</a>specified
viaan<a>tag.Usea\"tel:\"URL
to<ahref="tel:4155551212">dialaphonenumber</a>.
</string>
别忘了
TextViewt2=(TextView)findViewById(R.id.text2);
t2.setMovementMethod(LinkMovementMethod.getInstance());
3:buildsthetextintheJavacodeusingHTML
TextViewt3=(TextView)findViewById(R.id.text3);
t3.setText(Html.fromHtml("<b>text3:</b>Textwitha"
+"<ahref=\"http://www.google.com\">link</a>"
+"createdintheJavasourcecodeusingHTML."));
t3.setMovementMethod(LinkMovementMethod.getInstance());
4:字符串截取方法
SpannableStringss=newSpannableString("text4:Clickheretodialthephone.");
ss.setSpan(newStyleSpan(Typeface.BOLD),0,6,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss.setSpan(newURLSpan("tel:4155551212"),13,17,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextViewt4=(TextView)findViewById(R.id.text4);
t4.setText(ss);
t4.setMovementMethod(LinkMovementMethod.getInstance());
Android中我们为了实现文本的滚动可以在ScrollView中嵌入一个TextView,其实TextView自己也可以实现多行滚动的,毕竟ScrollView必须只能有一个直接的子类布局。只要在layout中简单设置几个属性就可以轻松实现
<TextView
android:id="@+id/tvCWJ"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"<!--垂直滚动条-->
android:singleLine="false"<!--实现多行-->
android:maxLines="15"<!--最多不超过15行-->
android:textColor="#FF0000"
/>
当然我们为了让TextView动起来,还需要用到TextView的setMovementMethod方法设置一个滚动实例,代码如下
TextViewtv=(TextView)findViewById(R.id.tvCWJ);
tv.setMovementMethod(ScrollingMovementMethod.getInstance());//Android开发网提示相关的可以查看SDK中android.text.method分支了解更多
ad_link=(TextView)findViewById(R.id.ad_link);
ad_link.setText(Html.fromHtml("<ahref="\"mce_href="\"""+mURL.getLink()+"\">"+Html.fromHtml(mURL.getLabel()+"</a>")));
ad_link.setMovementMethod(LinkMovementMethod.getInstance());
9.DecorView和ViewRoot的关系
ActivityThread.java中调用wm.addView(decor,l);把它加入到windowmanagerproxy的mViews中,同时为这个decorview创建一个ViewRoot,ViewRoot负责协调decorview与windowmanager直接绘图、事件处理。
ViewRoot中有IWindowSession和IWindow用来和windowmanger打交道和接收windowmanager传过来的消息,消息传过来后ViewRoot分发给decorview,再由decorview进行分发,windowmanagerproxy中维护了view,ViewRoot,layoutparam三元组。每次调用windowmanagerproxy的addView都会新增一个三元组。一般程序中都是调用addView(decor,...),也就是只对decorview调用addView。
10.androidHome键屏蔽,捕获,修改
开发过程中相信大家都有碰到因为不能捕获Home键而烦恼,现在终于有办法了,在Level5以上(包含)中,Activity类中有如下方法:
publicvoidonAttachedToWindow()
Since:APILevel5
Calledwhenthemainwindowassociatedwiththeactivityhasbeenattachedtothewindowmanager.SeeView.onAttachedToWindow()formoreinformation.
SeeAlso
*onAttachedToWindow()
privatebooleancatchHomeKey=false;
@Override
publicvoidonAttachedToWindow(){
//TODOAuto-generatedmethodstub
if(catchHomeKey){
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
}
super.onAttachedToWindow();
}
@Override
publicbooleanonKeyDown(intkeyCode,KeyEventevent){
//TODOAuto-generatedmethodstub
if(keyCode==KeyEvent.KEYCODE_HOME){
Log.e(TAG,"Homekeydown");
}
returnsuper.onKeyDown(keyCode,event);
}
重写Activity中的onAttachedToWindow方法,设置Type,就能捕获到Home键。
当不需要捕获时,删除setType这一行就OK。
11.获取Android的SdkVersion版本号
intversion=Integer.valueOf(android.os.Build.VERSION.SDK);
12.获取手机本地参数
入口:TelephonyManagertm=(TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
13.获取信号强度信息
publicclassGetGsmSignalStrengthextendsActivity
{
/*Thisvariablesneedtobeglobal,sowecanusedthemonResumeandonPausemethodto
stopthelistener*/
TelephonyManagerTel;
MyPhoneStateListenerMyListener;
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(BundlesavedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/*Updatethelistener,andstartit*/
MyListener=newMyPhoneStateListener();
Tel=(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
Tel.listen(MyListener,PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
}
/*Calledwhentheapplicationisminimized*/
@Override
protectedvoidonPause()
{
super.onPause();
Tel.listen(MyListener,PhoneStateListener.LISTEN_NONE);
}
/*Calledwhentheapplicationresumes*/
@Override
protectedvoidonResume()
{
super.onResume();
Tel.listen(MyListener,PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
}
/*—————————–*/
/*StartthePhoneStatelistener*/
/*—————————–*/
privateclassMyPhoneStateListenerextendsPhoneStateListener
{
/*GettheSignalstrengthfromtheprovider,eachtiomethereisanupdate*/
@Override
publicvoidonSignalStrengthsChanged(SignalStrengthsignalStrength)
{
super.onSignalStrengthsChanged(signalStrength);
Toast.makeText(getApplicationContext(),"GotoFirstdroid!!!GSMCinr="
+String.valueOf(signalStrength.getGsmSignalStrength()),Toast.LENGTH_SHORT).show();
}
};/*EndofprivateClass*/
}/*GetGsmSignalStrength*/
需要添加权限:
<uses-permissionandroid:name="android.permission.CHANGE_NETWORK_STATE"></uses>
14.自定义View设置Shadow阴影
TextView可以利用shadowColor来设置文字阴影,这里分享另一种设置阴影的方法
可以利用Paint的setShadowLayer实现自定义View的shadow。
packagecom.devdiv.myshadow2;
importandroid.app.Activity;
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Paint.Style;
importandroid.os.Bundle;
importandroid.view.View;
publicclassMyShadow2extendsActivity{
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(newdrawCanvas(this));
}
classdrawCanvasextendsView{
publicdrawCanvas(Contextcontext){
super(context);
}
@Override
protectedvoidonDraw(Canvascanvas){
super.onDraw(canvas);
//建立Paint对象
PaintvPaint=newPaint();
PaintvPaint2=newPaint();
//--------------------------------------------
//设定颜色
vPaint.setColor(0xFFFFFF00);
//实心矩形,看左上矩形
canvas.drawRect(30
,50
,130
,150
,vPaint
);
//设定阴影(柔边,X轴位移,Y轴位移,阴影颜色)
vPaint.setShadowLayer(5,3,3,0xFFFF00FF);
//实心矩形&其阴影,看左下矩形
canvas.drawRect(30
,200
,130
,300
,vPaint
);
//--------------------------------------------
//设定颜色
vPaint2.setColor(0xFFFFFF00);
//空心
vPaint2.setStyle(Style.STROKE);
//空心矩形,看右上矩形
canvas.drawRect(200
,50
,300
,150
,vPaint2
);
//设定阴影(柔边,X轴位移,Y轴位移,阴影颜色)
vPaint2.setShadowLayer(5,3,3,0xFFFF00FF);
//空心矩形&其阴影,看右下矩形
canvas.drawRect(200
,200
,300
,300
,vPaint2
);
}
}
}