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:Video Graphics Array,即:显示绘图矩阵,相当于640×480 像素;
HVGA:Half-size VGA;即:VGA的一半,分辨率为480×320;
QVGA:Quarter VGA;即:VGA的四分之一,分辨率为320×240;
WVGA:Wide Video Graphics Array;即:扩大的VGA,分辨率为800×480像素;
WQVGA:Wide Quarter VGA;即:扩大的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的default window对应一个PhoneWindow, PhoneWindow中有过一个DecorView,它是所有view的根。
一个activity可以拥有一个或者多个window(如使用popup dialog)
另外,android支持surfaceView,这样一个window就可以拥有多个surface了
window->view hierachy(DecorView是tree的root)->ViewRoot->Surface
某一个view->surface
surfaceview是在view hierachy中embedded的surface
surfaceView类似于symbian中的DSA,直接访问surface
普通的是通过view访问surface
window manager会通过layer协调各个surface画图到frame buffer中去
一个activity可以拥有一个或者多个window(如使用popup dialog)
另外,android支持surfaceView,这样一个window就可以拥有多个surface了
window->view hierachy(DecorView是tree的root)->ViewRoot->Surface
某一个view->surface
surfaceview是在view hierachy中embedded的surface
surfaceView类似于symbian中的DSA,直接访问surface
普通的是通过view访问surface
window manager会通过layer协调各个surface画图到frame buffer中去
4:Canvas
android绘图最终绘制到surface上,它由window manager维护。
canvas相当于surface的设备环境,它提供了绘制的方法,并且指向surface
canvas是ViewRoot向window manager请求获取的,ViewRoot是客户端窗口(decor view及其child view)与Window Manager(surface)的桥梁,ViewRoot存在于window manager proxy中。
所有客户端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事件的接收和传递。
关于返回值的问题,基本规则很清楚,如果return true,那么表示该方法消费了此次事件,如果return false,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理。
SDK给出的说明如下:
· You will receive the down event here.
· The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.
· For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().
· If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here.
由于onInterceptTouchEvent()的机制比较复杂,上面的说明写的也比较复杂,总结一下,基本的规则是:
1.down事件首先会传递到onInterceptTouchEvent()方法
2.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
3.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的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方法中实现的:
case MotionEvent.ACTION_DOWN:
mPrivateFlags |= PRESSED;
refreshDrawableState();
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick();
}
break;
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PRESSED) != 0) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
if (!focusTaken) {
performClick();
}
}
…
break;
可以看到,Click的触发是在系统捕捉到ACTION_UP后发生并由performClick()执行的,performClick里会调用先前注册的监听器的onClick()方法:
public boolean performClick() {
…
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
LongClick的触发则是从ACTION_DOWN开始,由postCheckForLongClick()方法完成:
private void postCheckForLongClick() {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
}
可以看到,在ACTION_DOWN事件被捕捉后,系统会开始触发一个postDelayed操作,delay的时间在Eclair2.1上为500ms,500ms后会触发CheckForLongPress线程的执行:
class CheckForLongPress implements Runnable {
…
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
…
}
如果各种条件都满足,那么在CheckForLongPress中执行performLongClick(),在这个方法中将调用onLongClick():
public boolean performLongClick() {
…
if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
}
…
}
从实现中可以看到onClick()和onLongClick()方法是由ACTION_DOWN和ACTION_UP事件捕捉后根据各种情况最终确定是否触发的,也就是说如果我们在一个Activity或者View中同时监听或者覆写了onClick(),onLongClick()和onTouchEvent()方法,并不意味着只会发生其中一种。
下面是一个onClick被触发的基本时序的Log:
04-05 05:57:47.123: DEBUG/TSActivity(209): onTouch ACTION_DOWN
04-05 05:57:47.263: DEBUG/TSActivity(209): onTouch ACTION_UP
04-05 05:57:47.323: DEBUG/TSActivity(209): onClick
可以看出是按ACTION_DOWN -> ACTION_UP -> onClick的次序发生的。
下面是一个onLongClick被触发的基本时序的Log:
04-05 06:00:04.133: DEBUG/TSActivity(248): onTouch ACTION_DOWN
04-05 06:00:04.642: DEBUG/TSActivity(248): onLongClick
04-05 06:00:05.083: DEBUG/TSActivity(248): onTouch ACTION_UP
可以看到,在保持按下的状态一定时间后会触发onLongClick,之后抬起手才会发生ACTION_UP。
7.onClick和onLongClick能同时发生吗?
要弄清楚这个问题只要理解Android对事件处理的所谓消费(consume)概念即可,一个用户的操作会被传递到不同的View控件和同一个控件的不同监听方法处理,任何一个接收并处理了该次事件的方法如果在处理完后返回了true,那么该次event就算被完全处理了,其他的View或者监听方法就不会再有机会处理该event了。
onLongClick的发生是由单独的线程完成的,并且在ACTION_UP之前,而onClick的发生是在ACTION_UP后,因此同一次用户touch操作就有可能既发生onLongClick又发生onClick。这样是不是不可思议?所以及时向系统表示“我已经完全处理(消费)了用户的此次操作”,是很重要的事情。例如,我们如果在onLongClick()方法的最后return true,那么onClick事件就没有机会被触发了。
下面的Log是在onLongClick()方法return false的情况下,一次触碰操作的基本时序:
04-05 06:00:53.023: DEBUG/TSActivity(277): onTouch ACTION_DOWN
04-05 06:00:53.533: DEBUG/TSActivity(277): onLongClick
04-05 06:00:55.603: DEBUG/TSActivity(277): onTouch ACTION_UP
04-05 06:00:55.663: DEBUG/TSActivity(277): onClick
可以看到,在ACTION_UP后仍然触发了onClick()方法。
8.TextView加入链接的所有方法
1:使用android:autoLink="all" 只需在textview中加入这个属性 在里面写的文字中包含网址、电话、email的会自动加入连接地址。
如:
<TextView xmlns: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:uses a string resource containing explicit <a> tags to specify
links.
如: <string name="link_text_manual"><b>text2:</b> This is some other
text, with a <a href="http://www.google.com">link</a> specified
via an <a> tag. Use a \"tel:\" URL
to <a href="tel:4155551212">dial a phone number</a>.
</string>
别忘了
TextView t2 = (TextView) findViewById(R.id.text2);
t2.setMovementMethod(LinkMovementMethod.getInstance());
3: builds the text in the Java code using HTML
TextView t3 = (TextView) findViewById(R.id.text3);
t3.setText(Html.fromHtml("<b>text3:</b> Text with a "
+ "<a href=\"http://www.google.com\">link</a> "
+ "created in the Java source code using HTML."));
t3.setMovementMethod(LinkMovementMethod.getInstance());
4:字符串截取方法
SpannableString ss = new SpannableString("text4: Click here to dial the phone.");
ss.setSpan(new StyleSpan(Typeface.BOLD), 0, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss.setSpan(new URLSpan("tel:4155551212"), 13, 17, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView t4 = (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方法设置一个滚动实例,代码如下
TextView tv = (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("<a href="\" mce_href="\"""+mURL.getLink()+"\">"+Html.fromHtml(mURL.getLabel()+"</a>")));
ad_link.setMovementMethod(LinkMovementMethod.getInstance());
9.DecorView 和 ViewRoot 的关系
ActivityThread.java中调用wm.addView(decor, l);把它加入到window manager proxy的mViews中,同时为这个decor view创建一个ViewRoot,ViewRoot负责协调decor view与window manager直接绘图、事件处理。
ViewRoot中有IWindowSession和IWindow用来和window manger打交道和接收window manager传过来的消息,消息传过来后ViewRoot分发给decor view,再由decor view进行分发,window manager proxy中维护了view, ViewRoot, layout param三元组。每次调用window manager proxy的addView都会新增一个三元组。一般程序中都是调用addView(decor,...),也就是只对decor view调用addView 。
10.android Home键 屏蔽,捕获,修改
开发过程中相信大家都有碰到因为不能捕获Home键而烦恼,现在终于有办法了,在Level5以上(包含)中,Activity类中有如下方法:
public void onAttachedToWindow ()
Since: API Level 5
Called when the main window associated with the activity has been attached to the window manager. See View.onAttachedToWindow() for more information.
See Also
* onAttachedToWindow()
private boolean catchHomeKey = false;
@Override
public void onAttachedToWindow() {
// TODO Auto-generated method stub
if(catchHomeKey) {
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
}
super.onAttachedToWindow();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if(keyCode == KeyEvent.KEYCODE_HOME) {
Log.e(TAG, "Home key down");
}
return super.onKeyDown(keyCode, event);
}
重写Activity中的onAttachedToWindow方法,设置Type,就能捕获到Home键。
当不需要捕获时,删除setType这一行就OK。
11.获取Android的SdkVersion版本号
int version = Integer.valueOf(android.os.Build.VERSION.SDK);
12.获取手机本地参数
入口:TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
13.获取信号强度信息
public class GetGsmSignalStrength extends Activity
{
/* This variables need to be global, so we can used them onResume and onPause method to
stop the listener */
TelephonyManager Tel;
MyPhoneStateListener MyListener;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/* Update the listener, and start it */
MyListener = new MyPhoneStateListener();
Tel = ( TelephonyManager )getSystemService(Context.TELEPHONY_SERVICE);
Tel.listen(MyListener ,PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
}
/* Called when the application is minimized */
@Override
protected void onPause()
{
super.onPause();
Tel.listen(MyListener, PhoneStateListener.LISTEN_NONE);
}
/* Called when the application resumes */
@Override
protected void onResume()
{
super.onResume();
Tel.listen(MyListener,PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
}
/* —————————– */
/* Start the PhoneState listener */
/* —————————– */
private class MyPhoneStateListener extends PhoneStateListener
{
/* Get the Signal strength from the provider, each tiome there is an update */
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength)
{
super.onSignalStrengthsChanged(signalStrength);
Toast.makeText(getApplicationContext(), "Go to Firstdroid!!! GSM Cinr = "
+ String.valueOf(signalStrength.getGsmSignalStrength()), Toast.LENGTH_SHORT).show();
}
};/* End of private Class */
}/* GetGsmSignalStrength */
需要添加权限:
<uses -permission android:name="android.permission.CHANGE_NETWORK_STATE"></uses>
14.自定义View设置Shadow阴影
TextView可以利用shadowColor来设置文字阴影,这里分享另一种设置阴影的方法
可以利用Paint的setShadowLayer实现自定义View的shadow。
package com.devdiv.myshadow2;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.view.View;
public class MyShadow2 extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( new drawCanvas(this) );
}
class drawCanvas extends View {
public drawCanvas(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 建立Paint对象
Paint vPaint = new Paint();
Paint vPaint2 = new Paint();
// --------------------------------------------
// 设定颜色
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
);
}
}
}