Android 程序员必须知道的
53 个知识点
1. android 单实例运行方法
我们都知道 Android
平台没有任务管理器,而内部 App
维护者一个 Activity history stack
来实现窗口显示和销毁,对于常规从快捷方式运行来看都是 startActivity
可能会使用 FLAG_ACTIVITY_NEW_TASK
标记来打开一个新窗口,比如 Launcher,所以考虑单任务的实现方法比较简单,首先
Android123 纠正下大家一种错误的方法就是直接在androidmanifest.xml
的 application
节点中加入 android:launchMode="singleInstance"这句,其实这样将不会起到任何作用,Apps
内部维护的历史栈作用于 Activity,我们必须在
activity 节点中加入
android:launchMode="singleInstance"
这句才能保证单实例,当然一般均加在主程序启动窗口的 Activity。
2. px 像素如何转为dip
设备独立像素:::
最近有网友问如何将 px
像素转为 dip
独立设备像素,由于 Android
的设备分辨率众多,
目前主流的为 wvga,而很多老的设备为
hvga 甚至低端的
qvga,对于兼容性来说使用
dip 无非是比较方便的,由于他和
分辨率无关和屏幕的密度大小有关,所以推荐使用。
px= (int) (dip*density+0.5f) //这里
android 开发网提示大家很多网友获取
density(密度)的方法存在问题,
从资源中获取的是静态定义的,一般为 1.0
对于 HVGA
是正好的,而对于 wvga
这样的应该从 WindowsManager
中获取,
WVGA 为
1.5 这里可以再补充一下
dip,sip
的知识
3. Android 中动态改变
ImageView 大小大小
很多网友可能发现在 layout.xml
文件中定义了 ImageView
的绝对大小后,无法动态修改以后的大小显示,其实 Android
平台在设计 UI
控件时考虑到这个问题,为了适应不同的 Drawable
可以通过在 xml
的相关 ImageView
中加入
android:scaleType="fitXY"
这行即可,但因为使用了缩放可能会造成当前 UI 有所变形。
使用的前提是限制 ImageView
所在的层,可以使用一个内嵌的方法限制显示。
4. 如何判断
Android 手机当前是否联网?
如果拟开发一个网络应用的程序,首先考虑是否接入网络,在 Android
手机中判断是否联网可以通过
ConnectivityManager
类的 isAvailable()方法判断,
首先获取网络通讯类的实例
ConnectivityManager cwjManager=(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
使用 cwjManager.getActiveNetworkInfo().isAvailable();
来返回是否有效,如果为 True
则表示当前 Android
手机已
经联网,可能是 WiFi
或 GPRS、HSDPA
等等,具体的可以通过 ConnectivityManager
类的 getActiveNetworkInfo()
方法判
断详细的接入方式,需要注意的是有关调用需要加入
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
这个权限,android
开发网提醒大家在真机上 Market
和 Browser
程序都使用了这个方法,来判断是否继续,同时在一些
网络超时的时候也可以检查下网络连接是否存在,以免浪费手机上的电力资源。
5. Drawable、、
Bitmap、、
Canvas 和和
Paint 的关系
很多网友刚刚开始学习 Android
平台,对于 Drawable、Bitmap、Canvas
和 Paint
它们之间的概念不是很清楚,其实它
们除了 Drawable
外早在 Sun
的 J2ME
中就已经出现了,但是在 Android
平台中,Bitmap、
Canvas 相关的都有所变化。
首先让我们理解下 Android
平台中的显示类是 View,但是还提供了底层图形类
android.graphics,今天所说的这些均
为 graphics
底层图形接口。
Bitmap - 称作位图,一般位图的文件格式后缀为
bmp,当然编码器也有很多如
RGB565、RGB888。作为一种逐像素的显
示对象执行效率高,但是缺点也很明显存储效率低。我们理解为一种存储对象比较好。
Drawable -
作为 Android 平下通用的图形对象,它可以装载常用格式的图像,比如
GIF、PNG、JPG,当然也支持
BMP,
当然还提供一些高级的可视化对象,比如渐变、图形等。
Canvas - 名为画布,我们可以看作是一种处理过程,使用各种方法来管理
Bitmap、GL
或者 Path
路径,同时它可以配
合 Matrix
矩阵类给图像做旋转、缩放等操作,同时 Canvas
类还提供了裁剪、选取等操作。
Paint - 我们可以把它看做一个画图工具,比如画笔、画刷。他管理了每个画图工具的字体、颜色、样式。 如果涉
及一些 Android
游戏开发、显示特效可以通过这些底层图形类来高效实现自己的应用。
6. Activity 切换导致的
onCreate 重复执行
部分网友会发现 Activity
在切换到后台或布局从横屏 LANDSCAPE
切换到 PORTRAIT,会重新切换
Activity 会触发一次
onCreate 方法,
我们可以在 androidmanifest.xml
中的 activit
元素加入这个属性
android:configChanges="orientation|keyboardHidden"
即可,
比如
<activity android:name=".android123"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
同时在 Activity
的 Java
文件中重载 onConfigurationChanged(Configuration newConfig)这个方法,
这样就不会在布局切换或窗口切换时重载 onCreate
等方法。
代码如下:
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
if (this.getResources().getConfiguration(). orientation == Configuration.ORIENTATION_LANDSCAPE)
{
//land
}
else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
{
//port
}
}
7. Android 的的
ImageButton 问题问题
很多网友对 Android
提供的 ImageButton
有个疑问,当显示 Drawable
图片时就不会再显示文字了,
其实解决的方法有两种,第一种就是图片中就写入文字,但是这样解决会增加程序体积,同时硬编码方式会影响多国语
言的发布。第二种解决方法很简单,通过分析可以看到 ImageButton
的 layout,我们可以直接直接继承,
添加一个 TextView,对齐方式为右侧即可实现
ImageButton 支持文字右侧显示。
8. Android 代码优化技术
1.Java 内存控制
对于字符串操作而言如果需要连加这样的操作建议使用 StringBuilder,经过调试不难发现如果你的字符串每次连加,
使用 String
需要的内存开销会远大于 StringBuilder,然后
Android 手机常规的运行内存大约在
128MB 左右,对于运行多任
务就需要考虑了,
Android 开发网提示因为 Java
有 GC
不需要手动释放那么分配的时候就要格外的小心,频繁的 GC
操作仍然
是很影响性能的,在调试时我们可以通过 logcat
查看内存释放情况。
2.循环使用
平时在访问一个属性的时候效率远比一个固定变量低,如果你的循环估计次数常常大于 5,
假设 xxx.GetLength()方法的值一般大于
5,
推荐这样写,
比如
for(int i=0;i<xxx.GetLength();i++)
这里 xxx.GetLength
在每次循环都要调用,必然会影响程序效率,在游戏开发中显得更为明显,
改进的方法应该为
int j=xxx.GetLength()
for(int i=0;i<j;i++)
3.图片的优化
在 Android
平台中 2
维图像处理库 BitmapFactory
做的比较智能,为了减少文件体积和效率,常常不用很多资源文件,
而把很多小图片放在一个图片中,有切片的方式来完成,在 J2ME
中我们这样是为了将少文件头而解决 MIDP
这些设备的问题,
而 Android
中虽然机型硬件配置都比较高,有关 Android G1
硬件配置可以参考 G1
手机参数以及评测,但是当资源多时这样
的运行效率还是令人满意的,至少 Dalvik
优化的还不是很够。
9. Android 开发进阶之
NIO 非阻塞包(一一)
对于 Android
的网络通讯性能的提高,我们可以使用 Java
上高性能的 NIO (New I/O)
技术进行处理,NIO
是从 JDK 1.4
开始引入的,NIO
的 N
我们可以理解为 Noblocking
即非阻塞的意思,相对应传统的 I/O,比如
Socket 的
accpet()、read()
这些方法而言都是阻塞的。 NIO
主要使用了 Channel
和 Selector
来实现,Java
的 Selector
类似 Winsock
的 Select
模式,
是一种基于事件驱动的,整个处理方法使用了轮训的状态机,如果你过去开发过 Symbian
应用的话这种方式有点像活动对象,
好处就是单线程更节省系统开销,NIO
的好处可以很好的处理并发,对于 Android
网游开发来说比较关键, 对于多点 Socket
连接而言使用 NIO
可以大大减少线程使用,降低了线程死锁的概率,毕竟手机游戏有 UI
线程,音乐线程,网络线程,管理
的难度可想而知,同时 I/O
这种低速设备将影响游戏的体验。
NIO 作为一种中高负载的
I/O 模型,相对于传统的
BIO (Blocking I/O)来说有了很大的提高,处理并发不用太多的线程,
省去了创建销毁的时间,如果线程过多调度是问题,同时很多线程可能处于空闲状态,大大浪费了 CPU
时间,同时过多的线
程可能是性能大幅下降,一般的解决方案中可能使用线程池来管理调度但这种方法治标不治本。使用 NIO
可以使并发的效率
大大提高。当然 NIO
和 JDK 7
中的 AIO
还存在一些区别,AIO
作为一种更新的当然这是对于 Java
而言,如果你开发过 Winsock
服务器,那么 IOCP
这样的 I/O
完成端口可以解决更高级的负载,当然了今天 Android123
主要给大家讲解下为什么使用 NIO
在 Android
中有哪些用处。
NIO 我们分为几个类型分别描述,作为
Java 的特性之一,我们需要了解一些新的概念,比如
ByteBuffer 类,Channel,
SocketChannel,ServerSocketChannel,Selector
和 SelectionKey。有关具体的使用,Android
开发网将在明天详细讲解。
网友可以在 Android SDK
文档中看下 java.nio
和 java.nio.channels
两个包了 解。
http://www.android123.com.cn/androidkaifa/695.html
了解下这种技术,看看在马上要做的项目中是否用得到
10. Android Theme 和和
Styles 内部定义解析
昨天我们讲到的有关在 AndroidManifest.xml
中定义 Activity
的 theme
方法来实现无标题的方法,在使用 xml
让你的
Activity 无标题方法 一文中讲到的,很多网友不明白为什么这样做,其实在
Android123 以前的文章中多次提到了
styles
样式定义方法,
今天 Android
开发网再次把一些网友回顾了解下 android
样式的内部定义。
在一个工程的 res/values/theme.xml
中我们可以方便的定义自己的风格主题,
比如下面的 cwjTheme
中我们使用了基于 android
内部的白色调的背景 Theme.Light,
设置 windowsNoTitle
为 true
代表没有标题,背景颜色我们使用了 android
内部定义的透明,
同时设置 listView
控件的样式为 cwjListView,xml
样式代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="cwjTheme" parent="android:Theme.Light">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:listViewStyle">@style/cwjListView</item>
</style>
有关 ListView
控件我们自定义的风格就是修改下系统 listview
这个控件的每行分隔符样式,这里我们在工程下
res/drawable
文件夹下放一个图片名为 list_selector 图片,这样我们的
cwjListView 的代码可以这样写
<style name="cwjListView" parent="@android:style/Widget.ListView">
<item name="android:listSelector">@drawable/list_selector</item>
</style>
</resources>
通过定义 style
可以设置更多,比如让 cwjListView
的字体颜色就加入 textAppearance
属性,
比如
<item name="textAppearance">@android:style/TextAppearance</item>
等等。
11.Android JSON 解析示例代码
来自 Google
官方的有关 Android
平台的 JSON
解析示例,如果远程服务器使用了 json
而不是 xml
的数据提供,在 Android
平台上已经内置的 org.json
包可以很方便的实现手机客户端的解析处理。下面 Android123
一起分析下这个例子,帮助
Android 开发者需要有关
HTTP 通讯、正则表达式、JSON
解析、appWidget
开发的一些知识。
public class WordWidget extends AppWidgetProvider { //appWidget
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.startService(new Intent(context, UpdateService.class)); //避免
ANR,所以
Widget 中开了个服务
}
public static class UpdateService extends Service {
@Override
public void onStart(Intent intent, int startId) {
// Build the widget update for today
RemoteViews updateViews = buildUpdate(this);
ComponentName thisWidget = new ComponentName(this, WordWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
}
public RemoteViews buildUpdate(Context context) {
// Pick out month names from resources
Resources res = context.getResources();
String[] monthNames = res.getStringArray(R.array.month_names);
Time today = new Time();
today.setToNow();
String pageName = res.getString(R.string.template_wotd_title, monthNames[today.month],
today.monthDay);
RemoteViews updateViews = null;
String pageContent = "";
try {
SimpleWikiHelper.prepareUserAgent(context);
pageContent = SimpleWikiHelper.getPageContent(pageName, false);
} catch (ApiException e) {
Log.e("WordWidget", "Couldn't contact API", e);
} catch (ParseException e) {
Log.e("WordWidget", "Couldn't parse API response", e);
}
//正则表达式处理,有关定义见下面的
SimpleWikiHelper 类
Pattern pattern = Pattern.compile(SimpleWikiHelper. WORD_OF_DAY_REGEX);
Matcher matcher = pattern.matcher(pageContent);
if (matcher.find()) {
updateViews = new RemoteViews(context.getPackageName(),R.layout.widget_word);
String wordTitle = matcher.group(1) ;
updateViews.setTextViewText(R.id.word_title, wordTitle);
updateViews.setTextViewText(R.id.word_type, matcher.group(2));
updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
String definePage = res.getString(R.string.template_define_url, Uri.encode(wordTitle));
Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage)); //这里是打开相
应的网页,所以 Uri
是 http
的 url,action
是 view
即打开 web
浏览器
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 /* no requestCode */,
defineIntent, 0 /* no flags */);
updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent); //单击
Widget 打开
Activity
} else {
updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
CharSequence errorMessage = context.getText(R.string.widget_error);
updateViews.setTextViewText(R.id.message, errorMessage);
}
return updateViews;
}
@Override
public IBinder onBind(Intent intent) {
// We don't need to bind to this service
return null;
}
}
}
有关网络通讯的实体类,以及一些常量定义如下:
public class SimpleWikiHelper {
private static final String TAG = "SimpleWikiHelper";
public static final String WORD_OF_DAY_REGEX =
"(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";
private static final String WIKTIONARY_PAGE =
"http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
"rvprop=content&format=json%s";
private static final String WIKTIONARY_EXPAND_TEMPLATES =
"&rvexpandtemplates=true";
private static final int HTTP_STATUS_OK = 200;
private static byte[] sBuffer = new byte[512];
private static String sUserAgent = null;
public static class ApiException extends Exception {
public ApiException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
} public ApiException(String detailMessage) {
super(detailMessage);
}
}
public static class ParseException extends Exception {
public ParseException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}
public static void prepareUserAgent(Context context) {
try {
// Read package name and version number from manifest
PackageManager manager = context.getPackageManager();
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
sUserAgent = String.format(context.getString(R.string.template_user_agent),
info.packageName, info.versionName);
} catch(NameNotFoundException e) {
Log.e(TAG, "Couldn't find package information in PackageManager", e);
}
}
public static String getPageContent(String title, boolean expandTemplates)
throws ApiException, ParseException {
String encodedTitle = Uri.encode(title);
String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";
String content = getUrlContent(String.format(WIKTIONARY_PAGE, encodedTitle, expandClause));
try {
JSONObject response = new JSONObject(content);
JSONObject query = response.getJSONObject("query");
JSONObject pages = query.getJSONObject("pages");
JSONObject page = pages.getJSONObject((String) pages.keys().next());
JSONArray revisions = page.getJSONArray("revisions");
JSONObject revision = revisions.getJSONObject(0);
return revision.getString("*");
} catch (JSONException e) {
throw new ParseException("Problem parsing API response", e);
}
}
protected static synchronized String getUrlContent(String url) throws ApiException {
if (sUserAgent == null) {
throw new ApiException("User-Agent string must be prepared");
}
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(url);
request.setHeader("User-Agent", sUserAgent) ; //设置客户端标识
try {
HttpResponse response = client.execute(request);
StatusLine status = response.getStatusLine();
if (status.getStatusCode() != HTTP_STATUS_OK) {
throw new ApiException("Invalid response from server: " +status.toString());
}
HttpEntity entity = response.getEntity();
InputStream inputStream = entity.getContent(); //获取
HTTP 返回的数据流
ByteArrayOutputStream content = new ByteArrayOutputStream();
int readBytes = 0;
while ((readBytes = inputStream.read(sBuffer)) != -1) {
content.write(sBuffer, 0, readBytes); //转化为字节数组流
}
return new String(content.toByteArray()); //从字节数组构建
String
} catch (IOException e) {
throw new ApiException("Problem communicating with API", e);
}
}
}
有关整个每日维基的 widget
例子比较简单,主要是帮助大家积累常用代码,
了解 Android
平台 JSON
的处理方式,毕竟很多 Server
还是 Java
的。
12.Android 中使用定时器
TimerTask 类介绍
在 Android
平台中需要反复按周期执行方法可以使用 Java
上自带的 TimerTask
类,
TimerTask 相对于
Thread 来说对于资源消耗的更低,
除了使用 Android
自带的 AlarmManager
使用 Timer
定时器是一种更好的解决方法。
我们需要引入 import java.util.Timer;
和 import java.util.TimerTask;
private Timer mTimer = new Timer(true);
private TimerTask mTimerTask;
mTimerTask = new TimerTask()
{
public void run()
{
Log.v("android123","cwj");
}
};
mTimer.schedule(mTimerTask, 5000,1000); //在
1 秒后每
5 秒执行一次定时器中的方法,
比如本文为调用 log.v
打印输出。
如果想取消可以调用下面方法,取消定时器的执行
while(!mTimerTask.cancel());
mTimer.cancel();
最后 Android123
提示大家,如果处理的东西比较耗时还是开个线程比较好,
Timer 还是会阻塞主线程的执行,更像是一种消息的执行方式。
当然比 Handler
的 postDelay
等方法更适合处理计划任务。
13.Android 应用应用
Icon 大小在不同分辨率下定义
对于 Android
平台来说,不同分辨率下 Icon
的大小设计有着不同的要求,对于目前主流的 HDPI
即 WVGA
级别来说,通常 hdpi
的应用 icon
大小为 72x72,而标准的
mdpi 即
hvga 为
48x48,对于目前
HTC 和
Motorola 推出的一些
QVGA 的使用了
ldpi,图
标为 32x32,
常见的 Android
图标大小设计规范如下表所示:
Launcher::::
36 x 36 px
48 x 48 px
72 x 72 px
MenuMenuMenu::::
36 x 36 px
48 x 48 px
72 x 72 px
Status Bar:
24 x 24 px
32 x 32 px
48 x 48 px
TabTabTab::::
24 x 24 px
32 x 32 px
48 x 48 px
Dialog::::
24 x 24 px
32 x 32 px
48 x 48 px
List View::::
24 x 24 px
32 x 32 px
48 x 48 px
对于 android
界面设计的安全色,如下:
而对于系统自带默认程序的图标,下面为
png 的透明格式,直接鼠标右键另存为即可:
看看 sdk
文档上的关于界面图标的详细说明。
14.Android 控件美化
Shape 你会用吗?
如果你对 Android
系统自带的 UI
控件感觉不够满意,可以尝试下自定义控件,我们就以 Button
为例, 很早以前 Android123
就写到过 Android Button
按钮控件美化方法里面提到了 xml
的 selector
构造。当然除了使用 drawable
这样的图片外今天
Android 开发网谈下自定义图形
shape 的方法,对于
Button 控件
Android 上支持以下几种属性
shape、gradient、stroke、
corners 等。
我们就以目前系统的 Button
的 selector
为例说下:
<shape>
<gradient
android:startColor="#ff8c00"
android:endColor="#FFFFFF"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
对于上面,这条 shape
的定义,分别为渐变,
在 gradient
中
startColor
属性为开始的颜色,
endColor 为渐变结束的颜色,
下面的 angle
是角度。接下来是 stroke
可以理解为边缘,
corners 为拐角这里
radius 属性为半径,最后是相对位置属性
padding。
对于一个 Button
完整的定义可以为
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<gradient
android:startColor="#ff8c00"
android:endColor="#FFFFFF"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item> <item android:state_focused="true" >
<shape>
<gradient
android:startColor="#ffc2b7"
android:endColor="#ffc2b7"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item> <item>
<shape>
<gradient
android:startColor="#ff9d77"
android:endColor="#ff9d77"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#fad3cf" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
</selector>
注意 Android123 提示大家,以上几个
item 的区别主要是体现在
state_pressed 按下或
state_focused 获
得焦点时,当当来判断显示什么类型,而没有 state_xxx
属性的 item
可以看作是常规状态下。
15. Android 开发者应该保持以下特质
Android123
推荐新手应该遵循
1. 深读
SDK 文档
2. 深读
SDK 的
APIDemo 和
Samples
3. 掌握
GIT 开源代码
4. 多了解
Android 开源项目,学习别人的手法写程序。
16. Android 数组排序常见方法
Android 的数组排序方式基本上使用了
Sun 原生的
Java API 实现,常用的有
Comparator 接口实现
compare 方法和
Comparable
接口的 compareTo 方法,我们对于一个数组列表比如
ArrayList 可以通过这两个接口进行排序和比较,这里
Android123
给大家一个例子
private final Comparator cwjComparator = new Comparator() {
private final Collator collator = Collator.getInstance();
public final int compare(Object a, Object b) {
CharSequence a = ((Item) a).sName;
CharSequence b = ((Item) b).sID;
return collator.compare(a, b);
}
};
我们的 ArrayList
对象名为 mList,则执行排序可以调用方法
Collections.sort(mList, cwjComparator);
17.Android 控件控件
TextProgressBar 进度条上显文字
Android 系统的进度条控件默认的设计的不是很周全,比如没有包含文字的显示,那么如何在
Android 进度条控件上显
示文字呢?
来自 Google
内部的代码来了解下,主要使用的 addView
这样的方法通过覆盖一层 Chronometer
秒表控件来实现,
整个代码如下
public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener {
public static final String TAG = "TextProgressBar";
static final int CHRONOMETER_ID = android.R.id.text1;
static final int PROGRESSBAR_ID = android.R.id.progress;
Chronometer mChronometer = null;
ProgressBar mProgressBar = null;
long mDurationBase = -1;
int mDuration = -1;
boolean mChronometerFollow = false;
int mChronometerGravity = Gravity.NO_GRAVITY;
public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TextProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TextProgressBar(Context context) {
super(context);
} //Android
开发网提示关键部分在这里
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
int childId = child.getId();
if (childId == CHRONOMETER_ID && child instanceof Chronometer) {
mChronometer = (Chronometer) child;
mChronometer.setOnChronometerTickListener(this);
// Check if Chronometer should move with with ProgressBar
mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
mChronometerGravity = (mChronometer.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK);
} else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
mProgressBar = (ProgressBar) child;
}
}
@android.view.RemotableViewMethod
public void setDurationBase(long durationBase) {
mDurationBase = durationBase;
if (mProgressBar == null || mChronometer == null) {
throw new RuntimeException("Expecting child ProgressBar with id " +
"'android.R.id.progress' and Chronometer id 'android.R.id.text1'");
}
// Update the ProgressBar maximum relative to Chronometer base
mDuration = (int) (durationBase - mChronometer.getBase());
if (mDuration <= 0) {
mDuration = 1;
}
mProgressBar.setMax(mDuration);
}
public void onChronometerTick(Chronometer chronometer) {
if (mProgressBar == null) {
throw new RuntimeException(
"Expecting child ProgressBar with id 'android.R.id.progress'");
}
// Stop Chronometer if we're past duration
long now = SystemClock.elapsedRealtime();
if (now >= mDurationBase) {
mChronometer.stop();
} int remaining = (int) (mDurationBase - now);
mProgressBar.setProgress(mDuration - remaining);
if (mChronometerFollow) {
RelativeLayout.LayoutParams params;
params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams();
int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin);
int leadingEdge = ((contentWidth * mProgressBar.getProgress()) /
mProgressBar.getMax()) + params.leftMargin;
int adjustLeft = 0;
int textWidth = mChronometer.getWidth() ;
if (mChronometerGravity == Gravity.RIGHT) {
adjustLeft = -textWidth;
} else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
adjustLeft = -(textWidth / 2);
}
leadingEdge += adjustLeft;
int rightLimit = contentWidth - params. rightMargin - textWidth;
if (leadingEdge < params.leftMargin) {
leadingEdge = params.leftMargin;
} else if (leadingEdge > rightLimit) {
leadingEdge = rightLimit;
}
params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams();
params.leftMargin = leadingEdge;
mChronometer.requestLayout();
}
}
}
18. Android 内存管理-SoftReference
的使用
很多时候我们需要考虑 Android
平台上的内存管理问题,Dalvik VM
给每个进程都分配了一定量的可用堆内存,当我们
处理一些耗费资源的操作时可能会产生 OOM
错误(OutOfMemoryError)这样的异常,Android123
观察了下国内的类似 Market
客户端设计,基本上都没有采用很好的内存管理机制和缓存处理。
如果细心的网友可能发现 Android Market
客户端载入时,每个列表项的图标是异步刷新显示的,但当我们快速的往下
滚动到一定数量比如 50
个,再往回滚动时可能我们看到了部分 App
的图标又重新开始加载,当然这一过程可能是从 SQLite
数据库中缓存的,但是在内存中已经通过类似 SoftReference
的方式管理内存。
在 Java
中内存管理,引用分为四大类,强引用 HardReference、弱引用
WeakReference、软引用
SoftReference 和虚引
用 PhantomReference。它们的区别也很明显,HardReference
对象是即使虚拟机内存吃紧抛出 OOM
也不会导致这一引用的对
象被回收,而 WeakReference
等更适合于一些数量不多,但体积稍微庞大的对象,在这四个引用中,它是最容易被垃圾回收
的,而我们对于显示类似 Android Market
中每个应用的 App Icon
时可以考虑使用 SoftReference
来解决内存不至于快速回
收,同时当内存短缺面临 Java VM
崩溃抛出 OOM
前时,软引用将会强制回收内存,最后的虚引用一般没有实际意义,仅仅观
察 GC
的活动状态,对于测试比较实用同时必须和 ReferenceQueue
一起使用。
对于一组数据,我们可以通过 HashMap
的方式来添加一组 SoftReference
对象来临时保留一些数据,同时对于需要反复
通过网络获取的不经常改变的内容,可以通过本地的文件系统或数据库来存储缓存,希望给国内做 App Store
这样的客户端
一些改进建议。
19. 反射在
Android 开发中的利弊
由于 Android 2.2
的推出,很多新的 API
加入导致很多项目移植需要考虑使用 Java
的反射机制 Reflection
来动态调用,
动态调用的好处就是不需要使用引用文件,直接通过 JDK
中声明好的方法直接调用,本身原理基于
JVM 的,从
Java 1.5 开始
支持,原理上就是根据类名而不实例化对象的情况下,获得对象的方法或属性而直接调用。
Android 开发时反射能帮助我们多少?
1. 有些网友可能发现
Android 的
SDK 比较封闭,很多敏感的方法常规的用户无法编译,我们如果翻看了 代码直接在反
射中声明动态调用即可。比如很多 internal
或 I
开头的 AIDL
接口均可以通过反射轻松调用。
2. 反射对于
Android123 来说更重要的是考虑到应用的兼容性,我们目前主要兼容从
Android 1.5 到
2.2 的项目,API
Level 从
3 到
8 可以方便的扩充,调用前我们预留一个标志位声明该
API 的最低以及最高的
API Level 为多少可以调用。
3. 对于调试
Java 的反射是功臣了,在
Logcat 中我们可以看到出错的地方肯定有类似
java.lang.reflect.XXX 的字样,
这种自检机制可以帮助我们方便的调试 Android
应用程序。
反射的缺点有哪些:
(1). 因为是动态执行的,效率自然没有预编译时引用现有的库效率高,就像平时我们
Win32 开发时,可以不用
h 文件,
直接通过 GetProcAddress
一样去动态获取方法的地址。当然效率要根据复杂程度而决定,一般稍微复杂的处理性能损失可
能超过 20%,对于一些复杂的涉及
Java 自动类型转换判断,执行时间可能是直接引用的上千倍,所以最终我们调试时必须考
虑性能问题。
(2). 因为反射是动态的,所以需要处理很多异常,不然
Dalvik 崩溃出
Force Close 的概率会大很多,很简单的一个反
射就需要至少 3
个异常捕获,本身 try-catch
效率就不是很高,自然进一步影响运行效率,对于 Android
开发我们必须考虑
这些问题。
(3). 反射因为导致代码臃肿,自然稍微复杂的几个方法实用反射将会导致代码可读性和维护性降低,如果很抽象的调
用 Android
开发网强烈不推荐这种方法。 最后要说的是 Reflection
并不是 Java
的专利, 微软的.Net
也同样支持,同时
更多的动态语言如 Ruby
等均支持这一特性。
20.AsyncTask 对比对比
Thread 加加
Handler
很多网友可能发现 Android
平台很多应用使用的都是 AsyncTask,而并非
Thread 和
Handler 去更新
UI,
这里 Android123
给大家说下他们到底有什么区别,我们平时应该使用哪种解决方案。
从 Android 1.5
开始系统将 AsyncTask
引入到 android.os
包中,过去在很早 1.1
和 1.0 SDK
时其实官方将其命名为
UserTask,其内部是
JDK 1.5 开始新增的
concurrent 库,做过
J2EE 的网友可能明白并发库效率和强大性,比
Java 原始的
Thread 更灵活和强大,但对于轻量级的使用更为占用系统资源。
Thread 是
Java 早期为实现多线程而设计的,比较简单不支持
concurrent 中很多特性在同步和线程池类中需要自己去实
现很多的东西,对于分布式应用来说更需要自己写调度代码,而为了 Android UI
的刷新 Google
引入了 Handler
和 Looper
机制,它们均基于消息实现,有事可能消息队列阻塞或其他原因无法准确的使用。
Android 开发网推荐大家使用
AsyncTask 代替
Thread+Handler 的方式,不仅调用上更为简单,经过实测更可靠一些,
Google 在
Browser 中大量使用了异步任务作为处理耗时的
I/O 操作,比如下载文件、读写数据库等等,它们在本质上都离不
开消息,但是 AsyncTask
相比 Thread
加 Handler
更为可靠,更易于维护,但 AsyncTask
缺点也是有的比如一旦线程开启即
dobackground
方法执行后无法给线程发送消息,仅能通过预先设置好的标记来控制逻辑,当然可以通过线程的挂起等待标志
位的改变来通讯,对于某些应用 Thread
和 Handler
以及 Looper
可能更灵活。
21. Android Drawable 叠加处理方法
大家可能知道 Bitmap
的叠加处理在 Android
平台中可以通过 Canvas
一层一层的画就行了,
而 Drawable
中如何处理呢?
除了使用 BitmapDrawable
的 getBitmap
方法将 Drawable
转换为 Bitmap
外,今天 Android123
给大家说下好用简单的 LayerDrawable
类,LayerDrawable
顾名思义就是层图形对象。
下面直接用一个简单的代码表示:
Bitmap bm = BitmapFactory.decodeResource(getResources(),R.drawable.cwj);
Drawable[] array = new Drawable[3];
array[0] = new PaintDrawable(Color.BLACK); //黑色
array[1] = new PaintDrawable(Color.WHITE); //白色
array[2] = new BitmapDrawable(bm); //位图资源
LayerDrawable ld = new LayerDrawable(array); //参数为上面的
Drawable 数组
ld.setLayerInset(1, 1, 1, 1, 1); //第一个参数
1 代表数组的第二个元素,为白色
ld.setLayerInset(2, 2, 2, 2, 2); //第一个参数
2 代表数组的第三个元素,为位图资源
mImageView.setImageDrawable(ld);
上面的方法中 LayerDrawable
是关键,Android
开发网提示 setLayerInset
方法原型为 public void setLayerInset (int index, int l, int t, int r, int b)
其中第一个参数为层的索引号,
后面的四个参数分别为 left、top、right
和 bottom。
对于简单的图片合成我们可以将第一和第二层的 PaintDrawable
换成 BitmapDrawable
即可实现简单的图片合成。
22. onRetainNonConfigurationInstance
和和 getLastNonConfigurationInstance
很多网友可能知道 Android
横竖屏切换时会触发 onSaveInstanceState,
而还原时会产生 onRestoreInstanceState,
但是 Android
的 Activity
类还有一个方法名为
onRetainNonConfigurationInstance
和 getLastNonConfigurationInstance
这两个方法。
我们可以通过
onRetainNonConfigurationInstance
代替 onSaveInstanceState,比如距离
2
@Override
public Object onRetainNonConfigurationInstance()
{
//这里需要保存的内容,在切换时不是
bundle 了,我们可以直接通过
Object 来代替
return obj;
}
在恢复窗口时,我们可以不使用 onRestoreInstanceState,而代替的是
getLastNonConfigurationInstance 方法。
我们可以直接在 onCreate
中使用,比如
Object obj = getLastNonConfigurationInstance();
最终 obj
的内容就是上次切换时的内容。
这里 Android123
提醒大家,每次 Activity
横竖屏切换时 onCreate
方法都会被触发。
23. Android 中中
String 资源文件的
format 方法方法
很多时候我们感性 Google
在设计 Android
时遵守了大量 MVC
架构方式,可以让写公共代码、美工和具体逻辑开发人员
独立出来。
有关 Android
的资源文件 values/strings.xml
中如何实现格式化字符串呢?
这里 Android123
举个简单的例子,以及最终可能会用到哪些地方。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">cwj_Demo</string>
<string name="hello">android
开发网</string>
</resources>
上面是一段简单的字符串资源文件,没有用到格式化,因为比较简单直接描述了意思,
当我们设计一个类似 Delete xxx File ?
的时候,我们可能需要在 Java
中动态获取 xxx
的名称,所以定义资源时使
用格式化可以轻松解决,不需要一堆 String
去拼接或 StringBuffer
一个一个 append
这样的愚蠢方法,
看例子
<string name="alert">Delete %1$s File</string>
这里%1$s
代表这是一个字符串型的,如果是整数型可以写为%1$d,类似
printf 这样的格式化字符串函数,
当然如果包含了多个需要格式化的内容,则第二个可以写为%2$s
或%2$d
了,那么最终在 Java
中如何调用呢?
看下面的
例子:
例一:
整数型的
<string name="alert">I am %1$d years old</string>
定义的是这样的
当然,我们杜绝意外情况,比如冒出个 secret
这样的 string
类型的,注意上面是%1$d
不是%1$s,所以默认标准的合并
成为
int nAge=23;
String sAgeFormat = getResources().getString(R.string.alert);
String sFinalAge = String.format(sAgeFormat, nAge);
这样执行完后,就组成了 I am 23 years old,是不是很方便啊.
当然了,下面看下 String
字符串时的情况.
例二:
字符串型的
String sName="cwj"
String sCity="Shanghai"
资源定义为
<string name="alert2">My name is %1$s , I am form %2$s</string>
则 Java
中只需要 String sInfoFormat = getResources().getString(R.string.alert2);
String sFinalInfo=String.format(sInfoFormat, sName, sCity);
我们看到了整个,整个定义类似 MFC
的 CString::Format
或 Mac OS
中的 NSLog,但是需要显示类似
C#中那样显示的标
出参数的数字,比如%1
或%n,这里数字代表参数的第
n 个。
本行最终 sFinalInfo
显示的内容为 My name is cwj , I am form Shanghai
。
当然了你有什么不懂的地方可以来函至 android123@163.com
24. Android 工程内嵌资源文件的两种方法
Android 软件一般处理大的资源通过
sdcard 比如在线下载资源到
sdcard,
而 apk
中内嵌资源或二进制文件时一般使用下面的两种方法:
方法一
res/raw 目录下存放,比如
cwj.dat 一个二进制文件,我们可以读取可以直接
InputStream is=context.getResources().openRawResource(R.raw.cwj);
方法二
工程根目录下的 assets
文件夹中存放,比如 assets/cwj.dat
这样我们使用下面的代码
AssetManager am = context.getAssets();
InputStream is = am.open(cwj.dat);
这里 Android123
提示大家 Google
的 Android
系统处理 Assert
有个 bug,在
AssertManager 中不能处理单个超过
1MB 的
文件,不然会报异常具体数值大家可以测试下传个稍大的文件,我们在两年前的文章中有提到,而第一种 raw
没这个限制可
以放个 4MB
的 Mp3
文件没问题。
25. Android 自定义
View 以及以及
layout 属性全攻略
对于 Android
系统的自定义 View
可能大家都熟悉了,对于自定义 View
的属性添加,以及 Android
的 Layout
的命名空
间问题,很多网友还不是很清楚,今天 Android123
一起再带大家温习一下
CwjView myView=new CwjView(context);
如果用于游戏或整个窗体的界面,
我们可能直接在 onCreate
中 setContentView(myView);
当然如果是控件,我们可能会需要从 Layout
的 xml
中声明,比
如
<cn.com.android123.CwjView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
当然,我们也可以直接从父类声明比如
<View class="cn.com.android123.CwjView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
上面我们仅用了父类 View
的两个属性,均来自 android
命名空间,而名称为 layout_width
或 layout_height,我们自
定义的控件可能有更多的功能,比如
<cn.com.android123.CwjView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
cwj:age="22"
cwj:university="sjtu"
cwj:city="shanghai"
/>
我们可以看到上面的三个属性,是我们自定义的。
作为标准 xml
规范,可能还包含了类似
xmlns:android="http://schemas.android.com/apk/res/android"
这样的语句,对于定义完整的 View,我们的命名空
间为 cwj,
这里可以写为
xmlns:cwj=http://schemas.android.com/apk/res/cn.com.android123.cwjView
或
xmlns:cwj=http://schemas.android.com/apk/res/android
都可以。
对于定义的 cwj
命名空间和 age、university
以及 city
的三个属性我们如何定义呢?
在工程的 res/values
目录中我们
新建一个 cwj_attr.xml
文件,编码方式为 utf-8
是一个好习惯,内容如下
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<declare-styleable name="CwjView">
<attr name="age" format="integer" />
<attr name="city" format="string" />
<attr name="university" format="string" />
</declare-styleable>
</resources>
这里我们可能对 format
不是很熟悉,目前 Android
系统内置的格式类型有 integer
比如 ProgressBar
的进度值,float
比如 RatingBar
的值可能是 3.5
颗星,boolean
比如 ToggleButton
的是否勾选,string
比如 TextView
的 text
属性,
当然除了我们常见的基础类型外,Android
的属性还有特殊的比如 color
是用于颜色属性的,可以识别为#FF0000
等类
型,当然还有 dimension
的尺寸类型,比如 23dip,15px,18sp
的长度单位,还有一种特殊的为 reference,一般用于
引用@+id/cwj @drawable/xxx
这样的类型。 当然什么时候用 reference
呢?
我们就以定义一个颜色为例子, <attr
name="red" format="color|reference" />
这里我们用了逻辑或的运算符,定义的红色是颜色类型的,同时可以被引
用
当然,对于我们自定义的类中,我们需要使用一个名为 obtainStyledAttributes
的方法来获取我们的定义。在我
们自定义 View
的构造方法(Context context, AttributeSet attrs)的重载类型中可以用
public CwjView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.cwj_attr);
mAge = a.getInteger(R.styleable.CwjView_age, 22);
mCity = a.getString(R.styleable.CwjView_city, "shanghai");
mUniversity= a.getString(R.styleable.CwjView_university, "sjtu");
a.recycle(); //Android123
提示大家不要忘了回收资源
}
这样类的全局成员变量 mAge、mCity
就获取了我们需要的内容,当然根据 layout
中的数值我们自定义的 CwjView
需要
动态的处理一些数据的情况,可以使用 AttributeSet
类的 getAttributeResourceValue
方法获取。
public CwjView(Context context, AttributeSet attrs)
{
super(context, attrs);
resId = attrs.getAttributeResourceValue("cn.com.android123.CwjView", "age", 100);
resId = attrs.getAttributeResourceValue("cn.com.android123.CwjView", "city", "shanghai");
//resID
就可以任意使用了
}
以上两种方法中,参数的最后一个数值为默认的,如果您有不明白的地方可以来函到 android123@163.com
我们会在第
一时间回复。
26. 自定义
Android 主题风格
theme.xml 方法方法
在 Android
中可以通过自定义主题风格方式来实现个性化以及复用,
首先我们创建 theme.xml
主题文件,保存位置为工程的 res/values/theme.xml
,
这里我们可以可以为主题起一个名称,比如 CWJ,这里去除了
xml 的文件头
<?xml version="1.0" encoding="utf-8"?>这行,
我们在工程中只需在 androidmanifest.xml
文件的 Activity
节点中加入
android:theme="@style/Theme.CWJ"
属性,则这个 Activity 就使用了这种主题风格,
整个 xml
的关键代码如下:
<resources>
<style name="Theme.CWJ" parent="android:Theme">
<item name="android:windowBackground">@drawable/android123</item>
</style>
</resources>
其中上面的代码中,我们定义设置全局 android:windowBackground
即背景值为/res/drawable
中的 android123
图片为
背景,更多的属性定义可以参考 view
的 layout xml
属性设置,比如我们设置所有字体颜色、大体大小和样式,可以在 style
节点中加入
<item name="android:textColor">#fff</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
当然我们可以将上面的 android123
的图片改进下,使用一个 xml
文件替代,比如使用 bitmap
对象,则
/res/drawable/android123.xml
的完整代码变为
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/cwj_image"
android:tileMode="repeat" />
这里我们使用了一个 bitmap
对象来解析 cwj_image
图片,当然这里可以识别各种类型的图片,
其中 android:tileMode
是 bitmap
的内部属性,其中 tileMode
设置为 repeat
代表重复,这样可以节省 bitmap
资源,
比如我们的背景是一层楼,那么全屏可以显示同样的为 5
层效果,而图片仅是一层大小,对于资源利用相对更高。
当然 bitmap
的属性 tileMode
的值为 repeat
外还有其他的值比如 clamp、mirror,这些值并没有在
SDK 中并没有找到定
义,通过上次 Android
开发网的 Android
自定义 View
以及 layout
属性全攻略 一文,我们可以联想到 bitmap
属于
android.graphics.Bitmap
包,由于是 android 框架,所以下载
git 的
base 包,找到该类,类的实例化时
android123 已经
在 Android
自定义 View
以及 layout
属性全攻略 说的很清楚,所以我们定位到 res\values
中找到 attr.xml
有关 bitmap
的
定义即可,有关 bitmap
的更多属性如 antialias、filter
和 dither
都可以找到使用。
27. android 调试工具
monkey 压力测试实战
很多 Android
开发者可能因为没有充分测试自己的软件造成很容易出现 FC(Force Close)的问题,这里我们可以通过使
用 Android
固件中自带的 monkey
工具来做软件的压力测试,monkey
工具可以模拟各种按键,触屏,轨迹球、activity
等事
件,这里 Android123
提示大家说白了 monkey
就是一个小猴子随机狂玩你的 android
软件,看看会不会产生异常。 具体
的使用我们通过 Android SDK
给我们的 adb
调试桥链接设备或模拟器,进入 Linux Shell
状态,当然我们可以输入 adb shell
获取设备的 shell,也可以直接通过
adb 命令执行,比如说
adb shell monkey 来查看
monkey 工具中的参数说明,如
图:
我们要测试的
apk 文件要在 android
设备中已经安装,当然模拟器中也可以测试的。执行 adb shell monkey -p
cn.com.android123.cwj -v 100
我们执行这句的中包含了 p 参数,这里代表已安装软件的
packageName,而
v 代表查看
monkey
生成的详细随机事件名,最后的数字 100
为我们测试的随机事件数量为 100.有关更多的测试方法,请查看上图中的参数,整
个测试比较简单单很有效,不妨试试。
28. 自定义
View
有关 Android
的自定义 View
的框架今天我们一起讨论下,对于常规的游戏,
我们在 View
中需要处理以下几种问题:
1.控制事件
2.刷新
View
3. 绘制
View
1. 对于控制事件今天我们只处理按键事件
onKeyDown,以后的文章中将会讲到屏幕触控的具体处理
onTouchEvent 以及
Sensor 重力感应等方法。
2. 刷新
view 的方法这里主要有
invalidate(int l, int t, int r, int b)
刷新局部,四个参数分别为左、上、右、
下。整个 view
刷新 invalidate(),刷新一个矩形区域
invalidate(Rect dirty) ,刷新一个特性
Drawable,
invalidateDrawable(Drawable drawable)
,执行 invalidate 类的方法将会设置
view 为无效,最终导致
onDraw 方法被重
新调用。由于今天的 view
比较简单,Android123
提示大家如果在线程中刷新,除了使用 handler
方式外,可以在 Thread
中
直接使用 postInvalidate
方法来实现。
3. 绘制
View 主要是
onDraw()中通过形参
canvas 来处理,相关的绘制主要有
drawRect、drawLine、drawPath
等等。
view 方法内部还重写了很多接口,其回调方法可以帮助我们判断出
view 的位置和大小,
比如
onMeasure(int, int) Called to determine the size requirements for this view and all of its children.
、
onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its
children 和
onSizeChanged(int, int, int, int) Called when the size of this view has changed.
具体的作用,
大家可以用 Logcat
获取当 view
变化时每个形参的变动。
下面 cwjView
是我们为今后游戏设计的一个简单自定义 View
框架,我们可以看到在 Android
平台自 定义 view
还是很简
单的,同时 Java
支持多继承可以帮助我们不断的完善复杂的问题。
public class cwjView extends View {
public cwjView(Context context) {
super(context);
setFocusable(true); //允许获得焦点
setFocusableInTouchMode(true); //获取焦点时允许触控
}
@Override
protected Parcelable onSaveInstanceState() { //处理窗口保存事件
Parcelable pSaved = super.onSaveInstanceState();
Bundle bundle = new Bundle();
//dosomething
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) { //处理窗口还原事件
Bundle bundle = (Bundle) state;
//dosomething
super.onRestoreInstanceState(bundle.getParcelable("cwj"));
return;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) //处理窗口大小变化事件
{
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec); //如果不让父类处理记住调用
setMeasuredDimension
}
@Override
protected void onLayout (boolean changed, int left, int top, int right, int bottom)
{
super.onLayout (changed,left,top, ight,bottom) ;
}
@Override
protected void onDraw(Canvas canvas) {
Paint bg = new Paint();
bg.setColor(Color.Red);
canvas.drawRect(0, 0, getWidth()/2, getHeight()/2, bg); //将
view 的左上角四分之一填充为红色
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event); //让父类处理屏幕触控事件
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) { //处理按键事件,响应的轨迹球事件为
public boolean
onTrackballEvent (MotionEvent event)
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
break;
case KeyEvent.KEYCODE_DPAD_CENTER: //处理中键按下
break;
default:
return super.onKeyDown(keyCode, event);
}
return true;
} } 上面我们可以看到
onMeasure 使用的是父类的处理方法,如果我们需要解决自定义
View 的大小,可以尝试下
面的方法
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
height = View.MeasureSpec.getSize(heightMeasureSpec);
width = View.MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width,height);//这里面是原始的大小,需要重新计算可以修改本行
//dosomething
}
29. Canvas 和和
Paint 实例实例
昨天我们在 Android
游戏开发之旅三 View
详解中提到了 onDraw
方法,
有关详细的实现我们今天主要说下 Android
的 Canvas
和 Paint
对象的使用实例。
Canvas 类主要实现了屏幕的绘制过程,其中包含了很多实用的方法,比如绘制一条路径、区域、贴图、画点、 画线、渲
染文本,下面是 Canvas
类常用的方法,当然 Android
开发网提示大家很多方法有不同的重载版本,参数更灵活。
void drawRect(RectF rect, Paint paint) //绘制区域,参数一为
RectF 一个区域
void drawPath(Path path, Paint paint) //绘制一个路径,参数一为
Path 路径对象
void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) //贴图,参数一就是我们常规的
Bitmap 对
象,参数二是源区域(Android123
提示这里是 bitmap),参数三是目标区域(应该在
canvas 的位置和大小),参数四是
Paint
画刷对象,因为用到了缩放和拉伸的可能,当原始 Rect
不等于目标 Rect
时性能将会有大幅损失。
void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) //画线,参数一起始点
的 x
轴位置,参数二起始点的 y 轴位置,参数三终点的
x 轴水平位置,参数四
y 轴垂直位置,最后一个参数为
Paint 画刷对
象。
void drawPoint(float x, float y, Paint paint) //画点,参数一水平
x 轴,参数二垂直
y 轴,第三个参数为
Paint
对象。
void drawText(String text, float x, float y, Paint paint) //渲染文本,Canvas
类除了上面的还可以描绘文字,
参数一是 String
类型的文本,参数二 x
轴,参数三 y
轴,参数四是 Paint
对象。
void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) //在路径上绘制文
本,相对于上面第二个参数是 Path
路径对象 从上面来看我们可以看出 Canvas
绘制类比较简单同时很灵活,实现一般的
方法通常没有问题,同时可以叠加的处理设计出一些效果,不过细心的网友可能发现最后一个参数均为 Paint
对象。如果我
们把 Canvas
当做绘画师来看,那么 Paint
就是我们绘画的工具,比如画笔、画刷、颜料等等。
Paint 类常用方法:
void setARGB(int a, int r, int g, int b)
设置 Paint
对象颜色,参数一为 alpha
透明通道
void setAlpha(int a)
设置 alpha 不透明度,范围为
0~255
void setAntiAlias(boolean aa) //是否抗锯齿
void setColor(int color) //设置颜色,
这里 Android
内部定义的有 Color
类包含了一些常见颜色定义
void setFakeBoldText(boolean fakeBoldText) //设置伪粗体文本
void setLinearText(boolean linearText) //设置线性文本
PathEffect setPathEffect(PathEffect effect) //设置路径效果
Rasterizer setRasterizer(Rasterizer rasterizer) //设置光栅化
Shader setShader(Shader shader) //设置阴影
void setTextAlign(Paint.Align align) //设置文本对齐
void setTextScaleX(float scaleX) //设置文本缩放倍数,1.0f
为原始
void setTextSize(float textSize) //设置字体大小
Typeface setTypeface(Typeface typeface) //设置字体,Typeface
包含了字体的类型,粗细,还有倾斜、颜色等。
void setUnderlineText(boolean underlineText) //设置下划线
最终 Canvas
和 Paint
在 onDraw
中直接使用
@Override
protected void onDraw(Canvas canvas) {
Paint paintRed=new Paint();
paintRed.setColor(Color.Red);
canvas.drawPoint(11,3,paintRed); //在坐标
11,3 上画一个红点
}
下一次 Android123
将会具体讲到强大的 Path
路径,和字体 Typeface
相关的使用。
30. View 类详解
在 Android
游戏开发之旅二中我们讲到了 View
和 SurfaceView
的区别,
今天 Android123
从 View
类开始着重的介绍 Android
图形显示基类的相关方法和注意点。
自定义 View
的常用方法:
onFinishInflate()
当 View 中所有的子控件均被映射成
xml 后触发
onMeasure(int, int)
确定所有子元素的大小
onLayout(boolean, int, int, int, int)
当 View 分配所有的子元素的大小和位置时触发
onSizeChanged(int, int, int, int)
当 view 的大小发生变化时触发
onDraw(Canvas) view
渲染内容的细节
onKeyDown(int, KeyEvent)
有按键按下后触发
onKeyUp(int, KeyEvent)
有按键按下后弹起时触发
onTrackballEvent(MotionEvent)
轨迹球事件
onTouchEvent(MotionEvent)
触屏事件
onFocusChanged(boolean, int, Rect)
当 View 获取或失去焦点时触发
onWindowFocusChanged(boolean)
当窗口包含的 view 获取或失去焦点时触发
onAttachedToWindow()当
view 被附着到一个窗口时触发
onDetachedFromWindow()
当 view 离开附着的窗口时触发,Android123
提示该方法和
onAttachedToWindow()
是相反的。
onWindowVisibilityChanged(int)
当窗口中包含的可见的 view 发生变化时触发
以上是 View
实现的一些基本接口的回调方法,一般我们需要处理画布的显示时,
重写 onDraw(Canvas)用的的是最多的:
@Override
protected void onDraw(Canvas canvas) {
//这里我们直接使用
canvas 对象处理当前的画布,比如说使用
Paint 来选择要填充的颜色
Paint paintBackground = new Paint();
paintBackground.setColor(getResources().getColor(R.color.xxx)); //从
Res 中找到名为
xxx 的
color 颜色定义
canvas.drawRect(0, 0, getWidth(), getHeight(), paintBackground); //设置当前画布的背景颜色为
paintBackground
中定义的颜色,以 0,0
作为为起点,以当前画布的宽度和高度为重点即整块画布来填充。
具体的请查看 Android123
未来讲到的 Canvas
和 Paint,在
Canvas 中我们可以实现画路径,图形,区域,线。
而 Paint
作为绘画方式的对象可以设置颜色,大小,甚至字体的类型等等。 }
当然还有就是处理窗口还原状态问题(一
般用于横竖屏切换),除了在
Activity 中可以调用外,开发游戏时我们尽量在
View 中使用类似
@Override
protected Parcelable onSaveInstanceState() {
Parcelable p = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("x", pX);
bundle.putInt("y", pY);
bundle.putParcelable("android123_state", p);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
dosomething(bundle.getInt("x"), bundle.getInt("y")); //获取刚才存储的
x 和
y 信息
super.onRestoreInstanceState(bundle.getParcelable("android123_state"));
return;
}
在 View
中如果需要强制调用绘制方法 onDraw,可以使用
invalidate()方法,它有很多重载版本,同时在线程中的
postInvailidate()方法将在
Android 游戏开发之旅六中的 自定义
View 完整篇讲到。
31. View 和和
SurfaceView
在 Android
游戏当中充当主要的除了控制类外就是显示类,在 J2ME
中我们用 Display
和 Canvas
来实现这些,而 Google
Android 中涉及到显示的为
view 类,Android
游戏开发中比较重要和复杂的就是显示和游戏逻辑的处理。这里我们说下
android.view.View
和 android.view.SurfaceView。SurfaceView
是从 View
基类中派生出来的显示类,直接子类有
GLSurfaceView
和 VideoView,可以看出
GL 和视频播放以及
Camera 摄像头一般均使用
SurfaceView,到底有哪些优势呢?
SurfaceView
可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了 SurfaceHolder
类,使用 getHolder
方法获取,相关的有:
Canvas lockCanvas()
Canvas lockCanvas(Rect dirty)
、
void removeCallback(SurfaceHolder.Callback callback)、
void unlockCanvasAndPost(Canvas canvas) //控制图形以及绘制,
而在 SurfaceHolder.Callback
接口回调中可以通过下面三个抽象类可以自己定义具体的实现,比如第一个更改格式和
显示画面。
abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
abstract void surfaceCreated(SurfaceHolder holder)
abstract void surfaceDestroyed(SurfaceHolder holder)
对于 Surface
相关的,Android
底层还提供了 GPU
加速功能,所以一般实时性很强的应用中主要使用 SurfaceView
而
不是直接从 View
构建,同时 Android123
未来后面说到的 OpenGL
中的 GLSurfaceView
也是从该类实现。
32. Android 程序内存管理必读
很多开发者都是从 J2ME
或 J2EE
上过来的,对于内存的使用和理解并不是很到位,Android
开发网本次给大家一些架构
上的指导,防止出现豆腐渣工程的出现。Android
作为以 Java
语言为主的智能平台对于我们开发一些高性能和质量的软件来
说了解 Android
程序内存管理机制是必须的。 Android
的 Dalvik VM
在基础方面和 Sun JVM
没有什么大的区别仅仅是字节码
的优化,我们要知道什么时候用 gc
什么时候用 recycle
以及到底用不用 finalization,因为
Java 对内存的分配只需要
new
开发者不需要显示的释放内存,但是这样造成的内存泄露问题的几率反而更高。
1.对于常规开发者而言需要了解
Java 的四种引用方式,比如强引用,软引用,弱引用以及虚引用。一些复杂些的程序
在长期运行很可能出现类似 OutOfMemoryError
的异常。
2.并不要过多的指望
gc,不用的对象可以显示的设置为空,比如
obj=null,这里
Android123 提示大家,java
的 gc
使
用的是一个有向图,判断一个对象是否有效看的是其他的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开
销是可想而知。
3.Android 为每个程序分配的对内存可以通过
Runtime 类的
totalMemory() freeMemory() 两个方法获取
VM 的一些内存
信息,对于系统 heap
内存获取,可以通过 Dalvik.VMRuntime
类的 getMinimumHeapSize()
方法获取最小可用堆内存,同时
显示释放软引用可以调用该类的 gcSoftReferences()
方法,获取更多的运行内存。
4.对于多线程的处理,如果并发的线程很多,同时有频繁的创建和释放,可以通过
concurrent 类的线程池解决线程创
建的效率瓶颈。
5. 不要在循环中创建过多的本地变量。 有关
Android 和
Java 的系统性能分析,Android123
将在以后的文章中详细讲
述如何调试 Java
分析内存泄露以及 Android
上的 gdb
调试器分析得出内存性能改进。
33. Android 中内嵌字体实现个性化
在 Android
中我们的应用可以灵活的内嵌自己的字体文件,实现各个手机上可以正常的显示个性化文字,我们都知道
TextView 的
setTypeface 方法可以设置目标文字的显示特性,比如字体、颜色、粗体、斜体等。 我们直接找一个
TrueTypeFont
的字体文件即.ttf,对于
Win32 系统的用户可以直接在
Windows/fonts 文件夹中能找到很多。 比如微软雅黑就不错,可是体
积太大,由于 Android
的 Assets
类有单个文件 1MB
体积的限制,我们先找个英文字体做测试。这里我们将字体文件
android123.ttf
放到工程的 assets 文件夹的
fonts 目录中。
Typeface tf = Typeface.createFromAsset(getAssets(), "fonts/android123.ttf");
TextView tv = (TextView)findViewById(R.id.text);
tv.setTypeface(tf); //设置
TextView 的风格
tv.setText("CWJ Test");
tv.setTextSize(12);
tv.setTextColor(Color.RED);
34. 获取和设置
ListView 的选择项
获取当前选中项 int curPos = listView.getFirstVisiblePosition();
当然是用 getItemAtPosition(int nPos)方法
也可以
,设置当前选择位置 listView.setSelectedPosition(lastPos) ;
对于基于 AbsListView 为基类的
ListView 等控
件均可以使用这种方法。
35. android.text.format 文件大小和日期解析类
很多网友可能直接将自己的 J2ME
项目生硬的移植到 Android
平台,其实 Google
为我们提供好了文件大小和时间日期解
析类,它位于 android.text.format
这个包中,它提供了强大的标准化解析方法:
1. IP 地址解析类 在
android.text.format.Formatter 中提供了
String formatIpAddress(int addr)
这个方法可以轻
松方便的将 socket
中的 int
型转成类似 127.0.0.1
的 IP
格式,需要注意的是 Linux
平台的字节顺序,即小字节序、低字节
序 little-endian。
2. 文件大小解析类 细心的网友可能还看到了
android.text.format.Formatter 中的
formatFileSize 方法,该方法
String formatFileSize (Context context, long number)
,第二个参数是 long
型,一般为 File
对象的最后修改时间或
创建时间的方法,最终返回类似 12KB、5Bytes
的值,20MB
的字符串。
3. 日期时间解析类 ,该类位于
android.text.format.DateFormat 这个
package 中,该类提供了
Java 中的三种时间对
象,Android123
提示大家下面三种方法为静态可以直接调用,如下:
final static CharSequence format(CharSequence inFormat, Date inDate) //传入
Date 对象
Given a format string and a Date object, returns a CharSequence containing the requested date.
final static CharSequence format(CharSequence inFormat, Calendar inDate) //Calendar
对象
Given a format string and a Calendar object, returns a CharSequence containing the requested date.
final static CharSequence format(CharSequence inFormat, long inTimeInMillis) //long
对象
Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a CharSequence containing the
requested date.
我们可能看到了第一个参数均为 inFormat
这是一个 CharSequence
接口的 String
类型,它提供了灵活的时间格式解析
字符串描述,Android
开发网提示大家注意大小写要区分,如
April 6, 1970 at 3:23am
例子,那么 inFormat 参数的写法和最终执行的结果如下对照,下面就以
Android123 的
CWJ
生日为例子如下
"MM/dd/yy h:mmaa" -> "11/03/87 11:23am"
"MMM dd, yyyy h:mmaa" -> "Nov 3, 1987 11:23am"
"MMMM dd, yyyy h:mmaa" -> "November 3, 1987 11:23am"
"E, MMMM dd, yyyy h:mmaa" -> "Tues, November 3, 1987 11:23am"
"EEEE, MMMM dd, yyyy h:mmaa" -> "Tuesday, Nov 3, 1987 11:23am"
对于判断一个时间是否为 24
小时制式可以通过 android.text.format.DateFormat
类的 static
boolean is24HourFormat(Context context)方法来判断。
36. Android 代码性能优化技巧
目前来说 Android 2.2
的 JIT
性能有了本质的提高,不过对于老版本的程序提高 Java
执行效率还有很多语言特点来说,
今天 Android123
提到的不是语法糖,而是基础的问题,对于 Java 1.5
之后将会有明显的改进。下面的例子来自 SDK: static
class Foo {
int mSplat;
}
Foo[] mArray = ...
上面的静态类 Foo 的执行效果和性能,我们分三个方法
zero、one
和 two
来做对比。
public void zero() { //大多数人可能简单直接这样写
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray.mSplat;
}
}
public void one() { //通过本地对象改进性能
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray.mSplat;
}
}
public void two() { //推荐的方法,通过
Java 1.5 的新语法特性可以大幅改进性能
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
zero() is slowest, because the JIT can't yet optimize away the cost of getting the array length once for
every iteration through the loop. one() is faster. It pulls everything out into local variables, avoiding the
lookups. Only the array length offers a performance benefit. two() is fastest for devices without a JIT, and
indistinguishable from one() for devices with a JIT. It uses the enhanced for loop syntax introduced in version
1.5 of the Java programming language.
37. Android 开发注意点
Part One
Android 已经的很多细节问题我们通过平台开发总结不断完善这个列表,如果你有相关的内容可以联
系 android123@163.com .
一、AssetManager -
已知单个文件处理不能大于 1MB,所以如果资源很大,建议使用
Zip 格
式压缩存放。
二、ScrollView 中嵌入
ListView - 这个作法可能会出现你的
ListView 仅仅显示
1 行半。 三、Android
自带的 Zip
处理类对文件名编码无法识别,也没有提供显示的设置方法,在 zlib
中写死了。 四、使用一些资源对象记
住关闭,比如对于文件流对象最后 FileOutputStream os = xxx; try {
//dosomething
} finally {
os.close(); //显示的使用
finally 关闭文件对象。
} 对于
Cursor 而言,在移动位置时首先判断
Cursor 是否为空,最终使用完仍然需要
close 方法,
如果重用,可以使用 deactivate
方法释放当前资源,通过 requery
方法再次查询。 五、SDK
中标记为 deprecated
字样
的,常规情况下是有更好的方法可以替代,短期内可以放心使用。这些方法一般高版本的 SDK
都可以向上兼容,目 前尚未发
现 Android
放弃某些 API
的支持。 六、Notification
的 Intent
无法传递到目标的 Activity,Service
和 Broardcast
没
有测试过,中途需要通过 PendingIntent,可能这里出现了问题。
38. Android 上上
HTTP 协议通讯状态获取
通常情况下轻量级的 Http
传输 Android
平台可以直接使用 Sun Java
的 HttpURLConnection
类方法处理,比如果自己定
义一次请求 header
可以通过 setRequestProperty
设置,而我们需要获取的 Http Web Server
状态可以通过
HttpURLConnection.getResponseCode()
的方法获取。 当然 Http 协议返回值常见的有
200 为成功,400
为请求错误,404
为未找到,500
为服务器内部错误,403
无权查看,302
为重定向等等。 对于 Android
平台提供更完善的 Apache
类有
HttpClient
、HttpPost、HttpResponse、HttpGet
和 HttpEntity,其中对于数据报头
header 构造通过
HttpEntity,而返回
状态值可以通过 HttpResponse
获取。 有关 Android
客户端和 Server
通讯类相关的开发我们将会在以后文章中做大量实
例介绍。
39. Android 布局布局
Java 代码构造法
一般情况下对于 Android
程序布局我们往往使用 XML
文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以
及执行效率,可以通过 Java
代码执行创建,虽然 Android
编译过的 xml
是二进制的,但是加载 xml
解析器的效率对于资源
占用还是比较大的,一般一个简单的 TextView,比如
<TextView
android:id="@+id/textControl "
android:layout_width="100px"
android:layout_height="wrap_content" />
可以等价于下面的 Java 代码: LinearLayout.LayoutParams
textParams = new LinearLayout.LayoutParams(100, LayoutParams.WRAP_CONTENT); //宽度为
100px,高为自适应最小的
高度 // setOrientation(VERTICAL);
设置布局为垂直 TextView textControl = new TextView(this);//如果从一
个 XXXLayout.,比如
LinearLayout 为
View 的基类时这里
this 应该换成为创建改类的
Context
textControl.setText("Android
开发网欢迎您");
addView( textControl, textParams );
当然 Java 处理效率比
XML 快得多,但是对于一个复杂界面的编写,可能需
要一些套嵌考虑,如果你思维灵活的话,使用 Java
代码来布局你的 Android
应用程序是一个更好的方法。
40. 测试测试
Android 软件性能主要方法
对于 Android
平台上软件的性能测试可以通过以下几种方法来分析效率瓶颈,目前 Google
在 Android
软件开发过程中
已经引入了多种测试工具包,比如 Unit
测试工程,调试类,还有模拟器的 Dev Tools
都可以直接反应执行性能。 1.
在
模拟器上的 Dev Tools
可以激活屏幕显示当前的 FPS,CPU
使用率,可以帮助我们测试一些 3D
图形界面的性能。 2.
一
般涉及到网络应用的程序,在效率上和网速有很多关系,这里需要多次的调试才能实际了解。 3.
对于逻辑算法的效率
执行,我们使用 Android
上最普遍的,计算执行时间来查看: long start = System.currentTimeMillis();
//android 开发网提示这里做实际的处理
do something
long duration = System.currentTimeMillis() - start;
最终 duration
保存着实际处理该方法需要的毫秒
数。这里类似 Win32
上的 GetTickCount,在
Win 32 和
Symbian 上都提供了高精度的性能计数器和低阶计时器,这里在
Dalvik
VM 上的
Java 层这种方法对于一般的应用足以。
4. GC 效率跟踪,如果你执行的应用比较简单,可以在
DDMS 中查看下
Logcat
的 VM
释放内存情况,大概模拟下那些地方可以缓存数据或改进算法的。 5.
线程的使用和同步,Android
平台上给我们
提供了丰富的多任务同步方法,但在深层上并没有过多的比如自旋锁等高级应用,不过对于 Service
和 appWidget
而言,他
们实际的产品中都应该以多线程的方式处理,以释放 CPU
时间,对于线程和堆内存的查看这些都可以在 DDMS
中看到。 更
多的调试和性能测试方法 Android123
将在以后的内容中出现。
41. Splash Screen 开场屏在
Android 中的实现
很多网友可能发现近期 Tencent
推出的手机 QQ Android
版包含了一个开场屏 Splash Screen
载入效果,通常游戏或大
型软件打开时可能需要一个释放解析资源的过程,需要一个前台的动画播放和后台的逻辑处理线程配合,当然对于简单的软
件也可以加一个 Splash Screen
作为美化。在 Android
平台上如何实现呢?
首先创建一个 Activirty,在
SetContentView
时直接通过 ImageView
创建一个全屏的图片,Android123
提示大家还要考虑好分辨率和当前设备一致,onCreate
添加代码
如下: new Handler().postDelayed(new Runnable(){ //
为了减少代码使用匿名 Handler
创建一个延时的调用
public void run() {
Intent i = new Intent(SplashScreen. this, Main.class); //通过
Intent 打开最终真正的主界面
Main
这个 Activity
SplashScreen.this.startActivity(i); //启动
Main 界面
SplashScreen.this.finish(); //关闭自己这个开场屏
}
}, 5000); //5
秒,够用了吧
42. Android 的的
Activity 你知多少呢?
看到这个标题很多网友肯定回答,我知道 Activity
是 Android
上的窗口基类,了解 Activity
的生命周期比如 onCreate
onStop 等,呵呵,按照这样说
Android123 还知道
Activity 的实现其实是从
ApplicationContext,而
ApplicationContext
是从 Context
这个抽象类派生而来的,当然我们看到显示的是 View
或者 ViewGroup,当然今天说的不是这些东西,而是很多
网友来问的 Android
为什么不设计一个任务管理器,当然从 Android 1.5
开始 ActivityManager
类提供了 restartPackage
可以关闭一个程序,需要加上<uses-permission android:name="android.permission.RESTART_PACKAGES"/>这个权限,不
过我们注意到,长按 Home
键可以看到以前程序的运行,同时可以快速的切换回来。这就是 Android
独有的程序生命周期管
理机制 Activity
历史栈。 我们在一个普通的程序主窗口 A
中打开了一个窗口 B,而窗口
B 打开了窗口
C,但是按下
Back
键后结果出乎了预期,是的这就是 Activity
的 history stack
的原因,在数据结构中栈是 FIFO
的,阻止我们不愿意看的情
况的发生则可以在打开新 Activity
时加上标记 FLAG_ACTIVITY_NO_HISTORY,代码如下: Intent i= new Intent(this,
cwj.class);
i.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); //Android
开发网提示大家相关的还有
Intent.FLAG_ACTIVITY_CLEAR_TOP,都试试
startActivity(i);
当然更多的程序 Activity 控制可以再
androidmanifest.xml 中定义。
43. JSONObject 在在
Android 上的应用
如果你过去开发过 AJAX
应用,相信对 JSONObject
不会陌生吧,作为基于 JavaScript
的数据交换格式,可以直接代替
Xml,这里
Android 从
1.0 开始就完全支持
JSONObject。在平时应用中直接引入
import org.json.JSONObject;即可方便使
用。当然同类的还有 SOAP。 在常规使用方便
JSONObject 对象可以实现类似
Bundle 或
Parcel 可以封装数据,代替一个
XML 的
ITEM,但最大的优势是可以执行一些简单的方法,比如说
getString、has、put、getBoolean、getInt
等数据类型的
存取操作。Android123
提示大家对于常规的项目开发,今天本文不考虑 Server
端的布局,在 Android
平台上处理这些比较
简单,主要是一些 http
的请求处理。可以直接引入 import org.apache.http.xxx
来实现 web server
层的数据交换,如果
你没有专业的 Server
开发技术,可以通过简单的 Web
配合 JSON
方式快速实现自己的交互式应用。
44. Android 高性能文件类
MemoryFile
很多网友抱怨 Android
处理底层 I/O
性能不是很理想,如果不想使用 NDK
则可以通过 MemoryFile
类实现高性能的文件
读写操作。MemoryFile
顾名思义就是内存文件的意思,如果你过去从事过 Win32
开发,那么它的原理就是 MapViewOfFile(),
当然开发过 Linux
的网友可能很快就联想到了 mmap(),是的该类就是他们的托管代码层封装,位于
android.os.MemoryFile
这个位置,从 Android 1.0
开始就被支持。 MemoryFile
适用于哪些地方呢?
对于 I/O
需要频繁操作的,主要是和外部
存储相关的 I/O
操作,MemoryFile
通过将 NAND
或 SD
卡上的文件,分段映射到内存中进行修改处理,这样就用高速的 RAM
代替了 ROM
或 SD
卡,性能自然提高不少,对于 Android
手机而言同时还减少了电量消耗。Android123
提示网友该类实现的
功能不是很多,直接从 Object
上继承,通过 JNI
的方式直接在 C
底层执行。 主要的构造方法 MemoryFile(String name, int
length) ,这里第二个参数为文件大小,需要说明的是
Android 的
MemoryFile 和传统的
mmap 还有一点点区别,毕竟是手机,
它内部的内存管理方式 ashmem
会从内核中回收资源。毕竟目前部分低端机型的 RAM
也比较吃紧。 synchronized
boolean allowPurging(boolean allowPurging) //允许
ashmem 清理内存,线程安全同步的方式。
void close() //关闭,因为在
Linux 内部
mmap 占用一个句柄,不用时一定要释放了
InputStream getInputStream()
返回读取的内容用 Java 层的
InputStream 保存
OutputStream getOutputStream()
把一个 OutputSream 写入到
MemoryFile 中
boolean isPurgingAllowed() //判断是否允许清理
int length() //返回内存映射文件大小 下面就是我们熟悉的,读写细节,主要是对字符数组的操作,这里大家要计算好
每个文件类型的占用,同时考虑到效率对于自己分配的大小考虑粒度对齐。
int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
具体的实际应用,Android
开发网
将在下次和大家讲到。
45. TextUtils 类类-Android
字符串处理类
对于字符串处理 Android
为我们提供了一个简单实用的 TextUtils
类,如果处理比较简单的内容不用去思考正则表达式
不妨试试这个在 android.text.TextUtils
的类,主要的功能如下:
是否为空字符 static
boolean isEmpty(CharSequence str)
拆分字符串 public static String[] split (String text, String expression)
,
Android 开发网提示大家仔细看例子如下
String.split() returns [''] when the string to be split is empty. This
returns []. This does not remove any empty strings from the result. For example split("a,", "," ) returns {"a",
""}. 拆分字符串使用正则
public static String[] split (String text, Pattern pattern)
确定大小写是否有效在当
前位置的文本 TextUtils.getCapsMode(CharSequence cs, int off, int reqModes)
使用 HTML
编码这个字符串 static
String TextUtils.htmlEncode(String s)
46. InputSream 输入流转
String 字符串,,,
Android 开发工具类
在 Android
平台上使用 Java
层处理 I/O
时主要使用流,这里 Android
开发网给大家一个方便的类,可以处理 InputStream
输入流转为 String
字符串,在效率上,我们使用了字符串拼接 StringBuilder
类减少内存碎片以及 BefferedReader
类实现
一个缓存。 private String Stream2String(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 16*1024); //强制缓存大小为
16KB,一般
Java 类默认为
8KB
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) ! = null) { //处理换行符
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
} }
47. layout 资源包含,,,
android 开发必读
有时候我们在一个 Android
程序中可能会复用布局文件,这时可以在一个 xml
文件中复用过去的布局文件, 但是和常规
的使用不同的是,需要加上类似包含头文件一样的 include
关键字,比如下面我们需要包含 layout
文件夹下的 view.xml
布
局文件,需要<include layout="@layout/view" />
这样下,完整的如下, 大家可以试一试。 <?xml version="1.0"
encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cwj"
/>
<include layout="@layout/view" />
<include android:id="@+id/block" layout="@layout/item" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/android123"
/>
</LinearLayout>
48.Android 控件开发之
ToggleButton 原理原理
在 Android
平台上比较有特色的就是 ToggleButton
控件,虽然它的功能和 CheckBox
有些类似,但是他们的用处还是有
一定的区别比如 ToggleButton
原本有图片装饰,通过 ToggleButton
可以很清楚的显示某些状态。它们均从 Button
为基类
的 CompoundButton
中实现,其真假事件从 Checkable
来实现。
public abstract class CompoundButton extends Button implements Checkable {
private boolean mChecked; //状态是否选中
private int mButtonResource;
private boolean mBroadcasting;
private Drawable mButtonDrawable;
//按钮的图标
private OnCheckedChangeListener mOnCheckedChangeListener;
//选中状态改变监听
private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
private static final int[] CHECKED_STATE_SET = {
R.attr.state_checked
};
public CompoundButton(Context context) {
this(context, null);
}
public CompoundButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompoundButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a =context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CompoundButton,
defStyle, 0);
Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
if (d != null) {
setButtonDrawable(d);
}
boolean checked = a .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);
setChecked(checked);
a.recycle(); //显式的
GC
}
public void toggle() {
setChecked(!mChecked);
}
@Override
public boolean performClick() {
toggle();
return super.performClick();
}
public boolean isChecked() {
return mChecked;
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState(); //更新当前状态的按钮图标
if (mBroadcasting) {
return;
}
mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
mBroadcasting = false;
}
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
mOnCheckedChangeWidgetListener = listener;
}
public static interface OnCheckedChangeListener {
void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
}
public void setButtonDrawable(int resid) {
if (resid != 0 && resid == mButtonResource) {
return;
}
mButtonResource = resid;
Drawable d = null;
if (mButtonResource != 0) {
d = getResources().getDrawable(mButtonResource);
}
setButtonDrawable(d);
}
public void setButtonDrawable(Drawable d) {
if (d != null) {
if (mButtonDrawable != null) {
mButtonDrawable.setCallback(null);
unscheduleDrawable(mButtonDrawable) ;
}
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == VISIBLE, false);
mButtonDrawable = d;
mButtonDrawable.setState(null);
setMinHeight(mButtonDrawable.getIntrinsicHeight());
}
refreshDrawableState();
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = super.dispatchPopulateAccessibilityEvent(event);
if (!populated) {
int resourceId = 0;
if (mChecked) {
resourceId = R.string.accessibility_compound_button_selected;
} else {
resourceId = R.string.accessibility_compound_button_unselected;
}
String state = getResources().getString(resourceId);
event.getText().add(state);
event.setChecked(mChecked);
}
return populated;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final Drawable buttonDrawable = mButtonDrawable;
if (buttonDrawable != null) {
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int height = buttonDrawable.getIntrinsicHeight();
int y = 0;
switch (verticalGravity) {
case Gravity.BOTTOM:
y = getHeight() - height;
break;
case Gravity.CENTER_VERTICAL:
y = (getHeight() - height) / 2;
break;
}
buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);
buttonDrawable.draw(canvas);
}
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
@Override
protected void drawableStateChanged() {
//android123 提示状态改变时需要更换按钮的图标
super.drawableStateChanged();
if (mButtonDrawable != null) {
int[] myDrawableState = getDrawableState();
mButtonDrawable.setState(myDrawableState);
invalidate();
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mButtonDrawable;
}
static class SavedState extends BaseSavedState {
boolean checked;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
checked = (Boolean)in.readValue(null);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
@Override
public String toString() {
return "CompoundButton.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " checked=" + checked + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
setFreezesText(true);
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.checked = isChecked();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
}
从上面来看我们知道 CompuundButton
的实现相对繁琐了些,主要是考虑状态是否已经选中等情况的消息通知,Android
开发网提醒大家而 ToggleButton
相对 CompuundButton
增加的给用户而言主要是开关的文字显示。
public class ToggleButton extends CompoundButton {
private CharSequence mTextOn;
private CharSequence mTextOff;
private Drawable mIndicatorDrawable;
private static final int NO_ALPHA = 0xFF;
private float mDisabledAlpha;
public ToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a =context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ToggleButton, defStyle, 0);
mTextOn = a.getText(com.android.internal.R. styleable.ToggleButton_textOn);
mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff);
mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f) ;
syncTextState();
a.recycle();
}
public ToggleButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyleToggle);
}
public ToggleButton(Context context) {
this(context, null);
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
syncTextState();
}
private void syncTextState() {
boolean checked = isChecked();
if (checked && mTextOn != null) {
setText(mTextOn);
} else if (!checked && mTextOff != null) {
setText(mTextOff);
}
}
public CharSequence getTextOn() {
return mTextOn;
}
public void setTextOn(CharSequence textOn) {
mTextOn = textOn;
}
public CharSequence getTextOff() {
return mTextOff;
}
protected void onFinishInflate() {
super.onFinishInflate();
updateReferenceToIndicatorDrawable(getBackground());
}
@Override
public void setBackgroundDrawable(Drawable d) {
super.setBackgroundDrawable(d);
updateReferenceToIndicatorDrawable(d);
}
private void updateReferenceToIndicatorDrawable(Drawable backgroundDrawable) {
if (backgroundDrawable instanceof LayerDrawable) {
LayerDrawable layerDrawable = (LayerDrawable) backgroundDrawable;
mIndicatorDrawable =layerDrawable.findDrawableByLayerId(com.android.internal.R.id.toggle);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mIndicatorDrawable != null) {
mIndicatorDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
}
}
}
49. AsyncTask 实例代码演示
Android 异步任务
上次我们讲到了 Android
提供了一个较线程更简单的处理多任务的方法 AsyncTask
异步任务类,相对于线程来说
AsyncTask 对于简单的任务处理更安全,其内部的实现方法使用了
Android 的
Handler 机制, 对于常见的文件下载可以使用
AsyncTask 类来处理,在
Browser 浏览器中就是用了该类下载
Web 服务器
URL 的
Favicon 图标。 首先
Android123 以简单
的下载例子演示该类的大致结构,如下
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls);
publishProgress((int) ((i / (float) count)100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
//最终我们执行
DownloadFilesTask().execute(url1, url2, url3); //即可。
//在
Android 浏览器中下载
Favicon 图标的实现如下:
class DownloadTouchIcon extends AsyncTask<String, Void, Bitmap> {
private final ContentResolver mContentResolver;
private final Cursor mCursor;
private final String mOriginalUrl;
private final String mUrl;
private final String mUserAgent;
/* package */
BrowserActivity mActivity;
public DownloadTouchIcon(BrowserActivity activity, ContentResolver cr, Cursor c, WebView view) { //构造
方法
mActivity = activity;
mContentResolver = cr;
mCursor = c;
mOriginalUrl = view.getOriginalUrl();
mUrl = view.getUrl();
mUserAgent = view.getSettings().getUserAgentString();
}
public DownloadTouchIcon(ContentResolver cr, Cursor c, String url) { //实现本类的构造
mActivity = null;
mContentResolver = cr;
mCursor = c;
mOriginalUrl = null;
mUrl = url;
mUserAgent = null;
}
@Override
public Bitmap doInBackground(String... values) { //返回
Bitmap 类型
String url = values[0];
AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
HttpGet request = new HttpGet(url);
HttpClientParams.setRedirecting(client.getParams() , true); //处理
302 等重定向问题
try {
HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() == 200) { //如果
OK
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream content = entity.getContent(); //将图标保存到
InputStream 中,因为是二进制内
容
if (content != null) {
Bitmap icon = BitmapFactory.decodeStream( //从流中取出
Bitmap,这里使用了
BitmapFactory
类的静态方法 decodeStream
content, null, null);
return icon;
}
}
}
} catch (IllegalArgumentException ex) {
request.abort();
} catch (IOException ex) {
request.abort();
} finally {
client.close();
}
return null;
} @Override
protected void onCancelled() {
if (mCursor != null) {
mCursor.close();
}
} @Override
public void onPostExecute(Bitmap icon) {
if (mActivity != null) {
mActivity.mTouchIconLoader = null;
} if (icon == null || mCursor == null || isCancelled()) {
return;
}
//最终图标要保存到浏览器的内部数据库中,系统程序均保存为
SQLite 格式,Browser
也不例外,因为图片是二进制的
所以使用字节数组存储数据库的 BLOB
类型
final ByteArrayOutputStream os = new ByteArrayOutputStream();
icon.compress(Bitmap.CompressFormat.PNG, 100, os); //将
Bitmap 压缩成
PNG 编码,质量为
100%存储
ContentValues values = new ContentValues(); //构造
SQLite 的
Content 对象,这里也可以使用
raw sql 代替
values.put(Browser.BookmarkColumns.TOUCH_ICON,os.toByteArray()); //写入数据库的
//Browser.BookmarkColumns.TOUCH_ICON
字段
if (mCursor.moveToFirst()) {
do {
mContentResolver.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
mCursor.getInt(0)),values, null, null);
} while (mCursor.moveToNext());
}
mCursor.close();
}
}
本次 Android
开发网通过两个 AsyncTask
类演示了多种类型的任务构造,这里大家注意返回类型,本节演示了 Android
平台上 Content Provider、AsyncTask、Bitmap、HTTP
以及 Stream
的相关操作,大家如何想很快提高开发水平其实只要理
解 Google
如何去实现 Android
系统常规构架就可以轻松入门谷歌移动平台。
50. Android 自定义
View 实例实例
AnalogClock 源码源码
针对 Android
底层 View
的直接构造很多网友没有实战经验,本次 Android
开发网结合目前平台开源代码一起通过
AnalogClock
类来理解 View 的直接继承。AnalogClock
就是 Home Screen
上的那个带有两根指针的表盘类。它的实现我们直
接从开源代码可以了解到:
public class AnalogClock extends View {
private Time mCalendar;
private Drawable mHourHand; //时针
private Drawable mMinuteHand; //分针
private Drawable mDial; //表盘背景
private int mDialWidth; //表盘宽度
private int mDialHeight; //表盘高度
private boolean mAttached; //附着状态
private final Handler mHandler = new Handler(); //定一个
Handler 类实现更新时间
private float mMinutes;
private float mHour;
private boolean mChanged; //时间是否改变
public AnalogClock(Context context) {
this(context, null);
}
public AnalogClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnalogClock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources r = mContext.getResources();
TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);
mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial); //加载表盘资源
if (mDial == null) {
mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
}
mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour); //加载时针图片资源
if (mHourHand == null) {
mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
}
mMinuteHand = a.getDrawable(com.android.internal.R. styleable.AnalogClock_hand_minute); //加载分针图片
if (mMinuteHand == null) {
mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
} mCalendar = new Time(); //获取当前系统时间
mDialWidth = mDial.getIntrinsicWidth(); //获取表盘图片的宽度
mDialHeight = mDial.getIntrinsicHeight(); //高度,同上
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();//注册一个消息过滤器,获取时间改变时区,改变
action
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED) ;
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
mCalendar = new Time();
onTimeChanged();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttached) {
getContext().unregisterReceiver(mIntentReceiver); //反注册消息过滤器
mAttached = false;
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
float hScale = 1.0f;
float vScale = 1.0f;
if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
hScale = (float) widthSize / (float) mDialWidth;
}
if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
vScale = (float )heightSize / (float) mDialHeight;
}
float scale = Math.min(hScale, vScale);
setMeasuredDimension(resolveSize((int) (mDialWidth * scale), widthMeasureSpec),
resolveSize((int) (mDialHeight * scale), heightMeasureSpec));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
} //主要的绘图重写
View 的
onDraw 方法,我们可以看到通过
canvas 实例直接屏幕
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
boolean changed = mChanged;
if (changed) {
mChanged = false;
}
int availableWidth = mRight - mLeft;
int availableHeight = mBottom - mTop;
int x = availableWidth / 2;