
https://github.com/RustFisher https://rustfisher.github.io/about/
相关代码请参阅: https://github.com/RustFisher/aboutView/blob/master/app/src/main/java/com/rust/aboutview/activity/RoundCornerActivity.java 美工同学指定了一个进度条样式 这斑斓的进度条,如果要自己画实在是劳民伤财。于是请美工切了一张素材。 如果用shape或者.9图片不太好处理这个条纹。转变思路,放置2张图片。一张作为背景(底,bottom),一张作为进度条图片(cover)。 进度改变时,改变上面图片的宽度。 这就要求上面的图片是圆角的。自定义ImageView,调用canvas.clipPath来切割画布。 public class RoundCornerImageView extends android.support.v7.widget.AppCompatImageView { private float mRadius = 18; private Path mClipPath = new Path(); private RectF mRect = new RectF(); public RoundCornerImageView(Context context) { super(context); } public RoundCornerImageView(Context context, AttributeSet attrs) { super(context, attrs); } public RoundCornerImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setRadiusDp(float dp) { mRadius = dp2px(dp, getResources()); postInvalidate(); } public void setRadiusPx(int px) { mRadius = px; postInvalidate(); } @Override protected void onDraw(Canvas canvas) { mRect.set(0, 0, this.getWidth(), this.getHeight()); mClipPath.reset(); // remember to reset path mClipPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CW); canvas.clipPath(mClipPath); super.onDraw(canvas); } private float dp2px(float value, Resources resources) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.getDisplayMetrics()); } } 每次绘制都切割一次圆角。记得调用Path.reset()方法。 回到我们要的进度条。布局文件中放置好层叠的图片。 <RelativeLayout android:id="@+id/progress_layout" android:layout_width="190dp" android:layout_height="10dp" android:layout_centerInParent="true"> <ImageView android:id="@+id/p_bot_iv" android:layout_width="190dp" android:layout_height="10dp" android:src="@drawable/shape_round_corner_bottom" /> <com.rustfisher.view.RoundCornerImageView android:id="@+id/p_cover_iv" android:layout_width="100dp" android:layout_height="10dp" android:scaleType="centerCrop" android:src="@drawable/pic_cover_blue_white" /> </RelativeLayout> 需要在代码中动态地改变cover的宽度;dialog中提供如下方法改变LayoutParams public void updatePercent(int percent) { mPercent = percent; mPercentTv.setText(String.format(Locale.CHINA, "%2d%%", mPercent)); float percentFloat = mPercent / 100.0f; final int ivWidth = mBotIv.getWidth(); RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mProgressIv.getLayoutParams(); int marginEnd = (int) ((1 - percentFloat) * ivWidth); lp.width = ivWidth - marginEnd; mProgressIv.setLayoutParams(lp); mProgressIv.postInvalidate(); } 显示出dialog并传入进度,就可以看到效果了。 这只是实现效果的一种方法,如果有更多的想法,欢迎和我交流~ RustFisher@github
新建项目,得到一个示例工程。本例中使用intl包来管理文字资源。 项目地址: https://github.com/RustFisher/localization_demo 步骤: 添加依赖项 - intl 创建文字资源文件 生成arb文件 新增和修改arb文件 根据arb生成dart文件 创建localization代理,新建一个类继承LocalizationsDelegate,和文字资源文件联系起来 MaterialApp中添加本地化代理和语言类型 使用文字资源 添加依赖项 pubspec.yaml添加依赖项flutter_localizations,然后运行一下flutter packages get。 dependencies: flutter: sdk: flutter # 添加下面的依赖项 flutter_localizations: sdk: flutter intl: 0.15.6 intl_translation: 0.16.7 编辑dart文件 新建app_strings.dart文件。 import 'dart:async'; import 'package:intl/intl.dart'; import 'package:flutter/widgets.dart'; class AppStrings { AppStrings(Locale locale) : _localeName = locale.toString(); final String _localeName; static Future<AppStrings> load(Locale locale) { return initializeMessages(locale.toString()) .then((Object _) { return new AppStrings(locale); }); } static AppStrings of(BuildContext context) { return Localizations.of<AppStrings>(context, AppStrings); } String title() { return Intl.message( 'Localization Demo', name: 'title', desc: '应用标题', locale: _localeName, ); } String click() => Intl.message( 'Click', name: 'click', desc: '点击', locale: _localeName, ); String helloFromDemo() => Intl.message( 'Hello~', name: 'helloFromDemo', desc: '一句问候', locale: _localeName, ); } 此时initializeMessages方法会显示警告,暂时不用管,生成arb文件后再添加引用。 生成arb文件 进入项目目录,运行intl的命令。 /e/ws/localization_demo $ flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/app_strings.dart 生成l10n/intl_messages.arb,内容如下。可以看出是JSON格式的文本。 { "@@last_modified": "2018-07-15T22:13:19.218221", "title": "Localization Demo", "@title": { "description": "应用标题", "type": "text", "placeholders": {} }, "click": "Click", "@click": { "description": "点击", "type": "text", "placeholders": {} }, "helloFromDemo": "Hello~", "@helloFromDemo": { "description": "一句问候", "type": "text", "placeholders": {} } } 新增和修改arb文件 前面生成了l10n/intl_messages.arb,我们可以把它当成模板。复制粘贴一下,同目录下得到intl_en.arb和intl_zh.arb。文件名规则可以自己定。 以intl_zh.arb为例: { "@@last_modified": "2018-07-15T22:13:19.218221", "title": "国际化示例App", "@title": { "description": "应用标题", "type": "text", "placeholders": {} }, "click": "点击", "@click": { "description": "点击", "type": "text", "placeholders": {} }, "helloFromDemo": "你好呀~", "@helloFromDemo": { "description": "一句问候", "type": "text", "placeholders": {} } } 这里也可以把intl_messages.arb删掉。本例保留这个文件。 根据arb生成dart文件 $ flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n \ --no-use-deferred-loading lib/app_strings.dart lib/l10n/intl_*.arb No @@locale or _locale field found in intl_en, assuming 'en' based on the file name. No @@locale or _locale field found in intl_messages, assuming 'messages' based on the file name. No @@locale or _locale field found in intl_zh, assuming 'zh' based on the file name. 暂时无视警告。 此时在app_strings.dart中添加对l10n/intl_messages.arb的引用。 import 'package:localization_demo/l10n/messages_all.dart'; 警告消失~ 更新了arb文件后,需要重新生成dart文件。 创建localization代理 创建localizations_delegate.dart。新建AppLocalizationsDelegate类继承LocalizationsDelegate,复写方法。 泛型指定为前面的AppStrings。 import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:localization_demo/app_strings.dart'; class AppLocalizationsDelegate extends LocalizationsDelegate<AppStrings> { @override Future<AppStrings> load(Locale locale) { return AppStrings.load(locale); } @override bool isSupported(Locale locale) => ['zh', 'en'].contains(locale.languageCode); // 支持的类型要包含App中注册的类型 @override bool shouldReload(AppLocalizationsDelegate old) => false; } MaterialApp中添加本地化代理和语言类型 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), localizationsDelegates: [ AppLocalizationsDelegate(), // 我们定义的代理 GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [ // 支持的语言类型 const Locale('en', 'US'), // English const Locale('zh', ''), ], home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } } 使用文字资源 获取到AppStrings的实例。 AppStrings appStrings = AppStrings.of(context); print(appStrings); // logcat: I/flutter ( 7478): Instance of 'AppStrings' 注意,在MaterialApp中使用文字资源时,因为context的关系,要使用onGenerateTitle。 onGenerateTitle: (context) { return AppStrings.of(context).title(); }, 支持语言的类型 代理isSupported方法中的语言类型最好是和App中supportedLocales的一致 @override bool isSupported(Locale locale) => ['zh', 'en'].contains(locale.languageCode); // App中`supportedLocales` supportedLocales: [ const Locale('en', 'US'), // English const Locale('zh', ''), ], 否则可能出现获取不到AppStrings的异常。 参考: https://flutter.io/tutorials/internationalization/
记录一种简单的方式实现字符串的国际化。 这里没有用到Intl包,而是将所需的字符串存放在一个map中。 步骤: MaterialApp中添加本地化代理和语言类型 创建文字资源文件 新建一个类继承LocalizationsDelegate,和文字资源文件联系起来 使用代理获取想要的文字资源 新建项目international_demo,得到一个带按钮示例工程。改造一下MaterialApp。 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), localizationsDelegates: [// 添加区域 // 准备在这里添加我们自己创建的代理 GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [ // 添加区域 const Locale('en', 'US'), // English const Locale('he', 'IL'), // Hebrew // 可以继续添加我们想要支持的语言类型 ], home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } } 创建资源文件 新建localization_src.dart,将字符串存入一个map中。 import 'package:flutter/material.dart'; class DemoLocalizations { DemoLocalizations(this.locale); final Locale locale; static DemoLocalizations of(BuildContext context) { return Localizations.of<DemoLocalizations>(context, DemoLocalizations); } static Map<String, Map<String, String>> _localizedValues = { 'en': { 'title': 'Hello World', }, 'es': { 'title': 'Hola Mundo', }, 'zh': { 'title': '你好呀', }, }; String get title { return _localizedValues[locale.languageCode]['title']; } } 这里仅以title为例,有英语、西班牙语和中文三种。 创建代理 新建一个类继承LocalizationsDelegate,复写3个方法。 import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:international_demo/localization_src.dart'; class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> { const DemoLocalizationsDelegate(); @override bool isSupported(Locale locale) => ['en', 'es', 'zh'].contains(locale.languageCode); @override Future<DemoLocalizations> load(Locale locale) { return SynchronousFuture<DemoLocalizations>(DemoLocalizations(locale)); } @override bool shouldReload(DemoLocalizationsDelegate old) => false; } 复写的load方法中,使用了SynchronousFuture。 使用代理 回到main.dart。 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), localizationsDelegates: [ DemoLocalizationsDelegate(),// 这是我们新建的代理 GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [ const Locale('en', 'US'), // English const Locale('he', 'IL'), // Hebrew const Locale('zh', ''), // 新添中文,后面的countryCode暂时不指定 ], home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } } 在_MyHomePageState中调用DemoLocalizations。 DemoLocalizations localizations = DemoLocalizations.of(context); print(localizations); // 打印出 I/flutter (25535): Instance of 'DemoLocalizations' 观察logcat,可知我们获得了DemoLocalizations的实例。 获取title DemoLocalizations.of(context).title 如果需要更多的字符串,需要我们手动在资源文件的map中添加。 这里仅以title为例。 参考: 官方文档 https://flutter.io/tutorials/internationalization/
Android Studio 2.3 API 25 从源码角度分析Handler机制。有利于使用Handler和分析Handler的相关问题。 Handler 简介 一个Handler允许发送和处理Message,通过关联线程的 MessageQueue 执行 Runnable 对象。 每个Handler实例都和一个单独的线程及其消息队列绑定。 可以将一个任务切换到Handler所在的线程中去执行。一个用法就是子线程通过Handler更新UI。 主要有2种用法: 做出计划,在未来某个时间点执行消息和Runnable 在其他线程规划并执行任务 要使用好Handler,需要了解与其相关的 MessageQueue, Message和Looper;不能孤立的看Handler Handler就像一个操作者(或者像一个对开发者开放的窗口),利用MessageQueue和Looper来实现任务调度和处理 // 这个回调允许你使用Handler时不新建一个Handler的子类 public interface Callback { public boolean handleMessage(Message msg); } final Looper mLooper; // Handler持有 Looper 的实例 final MessageQueue mQueue; // 持有消息队列 final Callback mCallback; 在Handler的构造器中,我们可以看到消息队列是相关的Looper管理的 public Handler(Callback callback, boolean async) { // 处理异常 mLooper = Looper.myLooper(); // 处理特殊情况... mQueue = mLooper.mQueue; // 获取的是Looper的消息队列 } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; // 获取的是Looper的消息队列 mCallback = callback; mAsynchronous = async; } Android是消息驱动的,实现消息驱动有几个要素: 消息的表示:Message 消息队列:MessageQueue 消息循环,用于循环取出消息进行处理:Looper 消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler 初始化消息队列 在Looper构造器中即创建了一个MessageQueue 发送消息 通过Looper.prepare初始化好消息队列后就可以调用Looper.loop进入消息循环了,然后我们就可以向消息队列发送消息, 消息循环就会取出消息进行处理,在看消息处理之前,先看一下消息是怎么被添加到消息队列的。 消息循环 Java层的消息都保存在了Java层MessageQueue的成员mMessages中,Native层的消息都保存在了Native Looper的 mMessageEnvelopes中,这就可以说有两个消息队列,而且都是按时间排列的。 为什么要用Handler这样的一个机制 因为在Android系统中UI操作并不是线程安全的,如果多个线程并发的去操作同一个组件,可能导致线程安全问题。 为了解决这一个问题,android制定了一条规则:只允许UI线程来修改UI组件的属性等,也就是说必须单线程模型, 这样导致如果在UI界面进行一个耗时较长的数据更新等就会形成程序假死现象 也就是ANR异常,如果20秒中没有完成 程序就会强制关闭。所以比如另一个线程要修改UI组件的时候,就需要借助Handler消息机制了。 Handler发送和处理消息的几个方法 1.void handleMessage( Message msg):处理消息的方法,该方法通常被重写。 2.final boolean hasMessage(int what):检查消息队列中是否包含有what属性为指定值的消息 3.final boolean hasMessage(int what ,Object object) :检查消息队列中是否包含有what好object属性指定值的消息 4.sendEmptyMessage(int what):发送空消息 5.final Boolean send EmptyMessageDelayed(int what ,long delayMillis):指定多少毫秒发送空消息 6.final boolean sendMessage(Message msg):立即发送消息 7.final boolean sendMessageDelayed(Message msg,long delayMillis):多少秒之后发送消息 与Handler工作的几个组件Looper、MessageQueue各自的作用: 1.Handler:它把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息 2.MessageQueue:管理Message,由Looper管理 3.Looper:每个线程只有一个Looper,比如UI线程中,系统会默认的初始化一个Looper对象,它负责管理MessageQueue, 不断的从MessageQueue中取消息,并将相对应的消息分给Handler处理 Handler.java (frameworks/base/core/java/android/os) // 将消息添加到队列前,先判断队列是否为null public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } // ...... // 将消息添加到队列中 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; // 将自己指定为Message的Handler if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } 从这里也不难看出,每个Message都持有Handler。如果Handler持有Activity的引用,Activity onDestroy后Message却仍然在队列中, 因为Handler与Activity的强关联,会造成Activity无法被GC回收,导致内存泄露。 因此在Activity onDestroy 时,与Activity关联的Handler应清除它的队列由Activity产生的任务,避免内存泄露。 消息队列 MessageQueue.java (frameworks/base/core/java/android/os) // 添加消息 boolean enqueueMessage(Message msg, long when) { // 判断并添加消息... return true; } Handler.sendEmptyMessage(int what) 流程解析 获取一个Message实例,并立即将Message实例添加到消息队列中去。 简要流程如下 // Handler.java // 立刻发送一个empty消息 sendEmptyMessage(int what) // 发送延迟为0的empty消息 这个方法里通过Message.obtain()获取一个Message实例 sendEmptyMessageDelayed(what, 0) // 计算消息的计划执行时间,进入下一阶段 sendMessageDelayed(Message msg, long delayMillis) // 在这里判断队列是否为null 若为null则直接返回false sendMessageAtTime(Message msg, long uptimeMillis) // 将消息添加到队列中 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) // 接下来是MessageQueue添加消息 // MessageQueue.java boolean enqueueMessage(Message msg, long when) 部分源码如下 public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } Handler 取消任务 removeCallbacksAndMessages 要取消任务时,调用下面这个方法 public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token); } 通过调用Message.recycleUnchecked()方法,取消掉与此Handler相关联的Message。 相关的消息队列会执行取消指令 void removeCallbacksAndMessages(Handler h, Object object) Message 和 MessageQueue 简介 Message Message 属于被传递,被使用的角色 Message 是包含描述和任意数据对象的“消息”,能被发送给Handler。 包含2个int属性和一个额外的对象 虽然构造器是公开的,但获取实例最好的办法是调用Message.obtain()或Handler.obtainMessage()。 这样可以从他们的可回收对象池中获取到消息实例 一般来说,每个Message实例握有一个Handler 部分属性值 /*package*/ Handler target; // 指定的Handler /*package*/ Runnable callback; // 可以组成链表 // sometimes we store linked lists of these things /*package*/ Message next; 重置自身的方法,将属性全部重置 public void recycle() void recycleUnchecked() 获取Message实例的常用方法,得到的实例与传入的Handler绑定 /** * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned. * @param h Handler to assign to the returned Message object's <em>target</em> member. * @return A Message object from the global pool. */ public static Message obtain(Handler h) { Message m = obtain(); m.target = h; return m; } 将消息发送给Handler /** * Sends this Message to the Handler specified by {@link #getTarget}. * Throws a null pointer exception if this field has not been set. */ public void sendToTarget() { target.sendMessage(this); // target 就是与消息绑定的Handler } 调用这个方法后,Handler会将消息添加进它的消息队列MessageQueue中 MessageQueue 持有一列可以被Looper分发的Message。 一般来说由Handler将Message添加到MessageQueue中。 获取当前线程的MessageQueue方法是Looper.myQueue() Looper 简介 Looper与MessageQueue紧密关联 在一个线程中运行的消息循环。线程默认情况下是没有与之管理的消息循环的。 要创建一个消息循环,在线程中调用prepare,然后调用loop。即开始处理消息,直到循环停止。 大多数情况下通过Handler来与消息循环互动。 Handler与Looper在线程中交互的典型例子 class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); // 为当前线程准备一个Looper // 创建Handler实例,Handler会获取当前线程的Looper // 如果实例化Handler时当前线程没有Looper,会报异常 RuntimeException mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); // Looper开始运行 } } Looper中的属性 Looper持有MessageQueue;唯一的主线程Looper sMainLooper;Looper当前线程 mThread; 存储Looper的sThreadLocal // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; // Handler会获取这个消息队列实例(参考Handler构造器) final Thread mThread; // Looper当前线程 ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。 Looper 方法 准备方法,将当前线程初始化为Looper。退出时要调用quit public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); // Looper实例存入了sThreadLocal } prepare方法新建 Looper 并存入 sThreadLocal sThreadLocal.set(new Looper(quitAllowed))ThreadLocal<T>类 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } 当要获取Looper对象时,从sThreadLocal获取 // 获取与当前线程关联的Looper,返回可以为null public static @Nullable Looper myLooper() { return sThreadLocal.get(); } 在当前线程运行一个消息队列。结束后要调用退出方法quit() public static void loop() 准备主线程Looper。Android环境会创建主线程Looper,开发者不应该自己调用这个方法。 UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。 public static void prepareMainLooper() { prepare(false); // 这里表示了主线程Looper不能由开发者来退出 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } 获取主线程的Looper。我们开发者想操作主线程时,可调用此方法 public static Looper getMainLooper() 请参考: http://rustfisher.github.io/2017/06/07/Android_note/Android-Handler/
Python2.7 中获取路径的各种方法 sys.path 模块搜索路径的字符串列表。由环境变量PYTHONPATH初始化得到。 sys.path[0]是调用Python解释器的当前脚本所在的目录。 sys.argv 一个传给Python脚本的指令参数列表。 sys.argv[0]是脚本的名字(由系统决定是否是全名) 假设显示调用python指令,如python demo.py,会得到绝对路径; 若直接执行脚本,如./demo.py,会得到相对路径。 os.getcwd() 获取当前工作路径。在这里是绝对路径。https://docs.python.org/2/library/os.html#os.getcwd __file__ 获得模块所在的路径,可能得到相对路径。 如果显示执行Python,会得到绝对路径。 若按相对路径来直接执行脚本./pyws/path_demo.py,会得到相对路径。 为了获取绝对路径,可调用os.path.abspath() os.path 中的一些方法 os.path.split(path) 将路径名称分成头和尾一对。尾部永远不会带有斜杠。如果输入的路径以斜杠结尾,那么得到的空的尾部。 如果输入路径没有斜杠,那么头部位为空。如果输入路径为空,那么得到的头和尾都是空。https://docs.python.org/2/library/os.path.html#os.path.split os.path.realpath(path) 返回特定文件名的绝对路径。https://docs.python.org/2/library/os.path.html#os.path.realpath 代码示例 环境 Win7, Python2.7 以/e/pyws/path_demo.py为例 #!/usr/bin/env python import os import sys if __name__ == '__main__': print "sys.path[0] =", sys.path[0] print "sys.argv[0] =", sys.argv[0] print "__file__ =", __file__ print "os.path.abspath(__file__) =", os.path.abspath(__file__) print "os.path.realpath(__file__) = ", os.path.realpath(__file__) print "os.path.dirname(os.path.realpath(__file__)) =", os.path.dirname(os.path.realpath(__file__)) print "os.path.split(os.path.realpath(__file__)) =", os.path.split(os.path.realpath(__file__)) print "os.getcwd() =", os.getcwd() 在/d中运行,输出为 $ python /e/pyws/path_demo.py sys.path[0] = E:\pyws sys.argv[0] = E:/pyws/path_demo.py __file__ = E:/pyws/path_demo.py os.path.abspath(__file__) = E:\pyws\path_demo.py os.path.realpath(__file__) = E:\pyws\path_demo.py os.path.dirname(os.path.realpath(__file__)) = E:\pyws os.path.split(os.path.realpath(__file__)) = ('E:\\pyws', 'path_demo.py') os.getcwd() = D:\ 在e盘中用命令行直接执行脚本 $ ./pyws/path_demo.py sys.path[0] = E:\pyws sys.argv[0] = ./pyws/path_demo.py __file__ = ./pyws/path_demo.py os.path.abspath(__file__) = E:\pyws\path_demo.py os.path.realpath(__file__) = E:\pyws\path_demo.py os.path.dirname(os.path.realpath(__file__)) = E:\pyws os.path.split(os.path.realpath(__file__)) = ('E:\\pyws', 'path_demo.py') os.getcwd() = E:\
本文简单介绍Android中的AsyncTask,并从源码角度分析它的流程和特点。 AsyncTask有助于使用UI线程。 这个类能让你不主动使用多线程或Handler,在UI线程进行后台操作并发布结果。 是一个在不用多线程和Handler的情况下的帮助类。AsyncTask适用于短时间的操作(最多几秒)。 如需长时间的线程操作,建议使用多线程包java.util.concurrent中的API,比如Executor,ThreadPoolExecutor 和 FutureTask AsyncTask任务的构成: 3种泛型:Params, Progress 和 Result 4个步骤:onPreExecute, doInBackground, onProgressUpdate 和 onPostExecute Google文档 用法简介 虚构一个计算任务 /** * 虚拟的计算任务 */ private class CalculationTask extends AsyncTask<Float, Integer, Float> { protected Float doInBackground(Float... inputs) { Log.d(TAG, "doInBackground thread ID = " + Thread.currentThread().getId()); long step = 0; float result = 0; for (float f : inputs) { // 假设这里有一些耗时的操作 result += f; } while (step < 5) { result += step; step++; publishProgress((int) step); } return result; } protected void onProgressUpdate(Integer... progress) { Log.d(TAG, "onProgressUpdate thread ID = " + Thread.currentThread().getId()); Log.d(TAG, "onProgressUpdate: " + progress[0]); } protected void onPostExecute(Float result) { Log.d(TAG, "onPostExecute thread ID = " + Thread.currentThread().getId()); Log.d(TAG, "任务执行完毕"); } } // 执行任务 new CalculationTask().execute(1.2f, 2.3f, 6.3f); /* logcat Main thread ID = 1 doInBackground thread ID = 8089 onProgressUpdate thread ID = 1 onProgressUpdate: 1 ... onProgressUpdate thread ID = 1 onProgressUpdate: 5 onPostExecute thread ID = 1 任务执行完毕 */ AsyncTask 使用的的泛型 AsyncTask使用的3种泛型 Params 送去执行的类型 Progress 后台计算的进度类型 Result 后台计算的结果 不用的泛型可以用Void表示。例如 private class MyTask extends AsyncTask<Void, Void, Void> { ... } 异步任务的4个步骤 异步任务执行时经过4个步骤 onPreExecute() UI线程在任务开始前调用这个方法。此方法常用来设置任务,比如在屏幕上显示一个进度条。 doInBackground(Params...) onPreExecute()执行完毕后立即在后台线程中执行。这一步用来执行耗时的后台计算。 这个方法接受异步任务的参数,返回最后的任务结果。这一步可以调用publishProgress(Progress...)通知出去一个或多个进度。这些进度值会被onProgressUpdate(Progress...)在UI线程收到。 onProgressUpdate(Progress...) 调用publishProgress(Progress...)后会在UI线程中执行。用来显示执行中任务的UI。 onPostExecute(Result) 后台任务执行完毕时被调用。最终结果会被传入这个方法。 取消任务 调用cancel(boolean)可随时取消任务。取消任务后isCancelled()会返回true。 调用这个方法后,后台任务doInBackground(Object[])执行完毕后会调用onCancelled(Object)而不再是onPostExecute(Object)。 为保证任务能被及时地取消,在doInBackground(Object[])中应该经常检查isCancelled()返回值 线程规则 Threading rules 一些线程规则 异步任务必须从UI线程启动 必须在UI线程实例化AsyncTask类 必须在UI线程调用execute(Params...) 不要手动调用onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) 同一个异步任务实例只能被执行一次。重复执行同一个异步任务实例会抛出异常(IllegalStateException)。 源码简析 需要解决的问题: AsyncTask是如何调用后台线程完成任务的?线程是如何调度的? AsyncTask使用Executor,利用WorkerRunnable和FutureTask来执行后台任务 private final WorkerRunnable<Params, Result> mWorker; // 实现了 Callable private final FutureTask<Result> mFuture; private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; } 使用Handler来进行线程调度。内部定义了一个类InternalHandler。 从execute(Params... params)方法切入 先看方法execute(Params... params),使用默认执行器,并传入参数 调用xecuteOnExecutor(Executor exec, Params... params) @MainThread // 指定在主线程执行 public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 先判断当前状态,如果状态不是Status.PENDING,则抛出异常。 否则进入Status.RUNNING状态,执行onPreExecute(),再由执行器启动任务。 @MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: // 同一个任务实例只能够执行一次 throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); // 开始进入后台线程执行任务 return this; } mWorker带着传进来的参数,mFuture实例化时已经将mWorker注入。参看构造函数 public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 在后台线程进行自定义的操作 这里面可以调用publishProgress方法 Result result = doInBackground(mParams); Binder.flushPendingCommands(); return postResult(result); // 发送最终结果 } }; mFuture = new FutureTask<Result>(mWorker) { // 依赖 mWorker @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } publishProgress方法通过主线程的Handler向外通知进度 @WorkerThread protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } } 后台任务执行完毕,postResult发送最终结果 private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); // 会走到finish方法 return result; } private void finish(Result result) { if (isCancelled()) { onCancelled(result); // 如果任务已经被取消了 } else { onPostExecute(result); // 通知任务执行完毕 } mStatus = Status.FINISHED; } 关于默认执行器 sDefaultExecutor 和线程池 源码中构建了一个线程池和一个自定义的执行器SerialExecutor。靠它们来执行后台任务。 参考源代码 public abstract class AsyncTask<Params, Progress, Result> { private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); // 核心线程至少2个,最多4个 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE_SECONDS = 30; public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); public static final Executor THREAD_POOL_EXECUTOR; // 实际执行者 static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } // 默认执行器的类 private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } } } backup at http://rustfisher.github.io/2017/06/22/Android_note/Android-AsyncTask/
Java Listener pattern 监听者模式 2016-5-12 监听者模式(观察者模式)能降低对象之间耦合程度。为两个相互依赖调用的类进行解耦。 便于进行模块化开发工作。不同模块的开发者可以专注于自身的代码。 监听者用来监听自已感兴趣的事件,当收到自已感兴趣的事件时执行自定义的操作。 在某些数据变化时,其他的类做出一些响应。处理数据(或者分发事件)的类主动投送消息,感兴趣 的类主动“订阅”消息。 监听者模式在Android中有大量的运用,相信大家都不会感到陌生。在Android开发中,Button控件的 点击事件就是监听者模式最常见的例子。 当Button被点击,执行了 OnClickListener.onClick;Activity中给这个Button设置了自己实现 的OnClickListener,并复写了onClick方法,就能执行自定义操作了。 Java代码实例 下面来用Java来实现监听者模式。 这个例子是给“计算类”持续地传入数据,处理好数据后,发出结果。感兴趣的类接收结果。 2个文件:AlgoCalculator.java;MainUser.java AlgoCalculator.java是计算部分,接收数据并进行计算。并将结果传递出去。 MainUser.java是调用方,将基本数据传入AlgoCalculator并监听结果。 package com.algo; import java.util.LinkedList; import java.util.List; public class AlgoCalculator { private List<short[]> mDataBuffer = new LinkedList<>(); public AlgoCalculator() { } // 定义一个Listener接口;可将一个boolean值传递出去 public interface ResultChangeListener { void onChange(boolean found); } private ResultChangeListener resultChangeListener; // 调用方能够设置并实现这个接口 public void setResultChangedListener(ResultChangeListener resultChangedListener) { this.resultChangeListener = resultChangedListener; } // 传输数据 public void setDataStream(short[] data) { checkData(data);// 处理数据方法 } // 处理数据,并送出结果 private void checkData(short[] data) { if (data.length == 0) { return; } long sum = 0; for (short b : data) { sum += b; } if (sum > 40) { resultChangeListener.onChange(true); // 数据处理结果 } else { resultChangeListener.onChange(false); } } } 主程序;调用方传入数据,获取结果 import com.algo.AlgoCalculator; public class MainUser { public static void main(String[] args) { AlgoCalculator algoCalculator = new AlgoCalculator(); // 初始化 // 设置监听器,并在里面增加要执行的动作 algoCalculator.setResultChangedListener(new AlgoCalculator.ResultChangeListener() { @Override public void onChange(boolean found) { System.out.println("result: " + found); } }); short[] data1 = {1, 2, 3,}; short[] data2 = {10, 20, 30}; short[] data3 = {6, 7, 8}; short[] data4 = {1, 1, 1}; // 传入数据 algoCalculator.setDataStream(data1); // output false algoCalculator.setDataStream(data2); // output true algoCalculator.setDataStream(data3); // output false algoCalculator.setDataStream(data4); // output false } } 在另外的类里,能够很方便地调用AlgoCalculator的计算能力并获取计算结果。 在这里,每传入一次数据,就能获取一个结果。如果每秒钟传入一次数据,每秒钟就能获取一个结果。 我们可以把复杂的算法封装起来,客户端只需要传入数据,即可获得(监听到)结果。 很多场景中都使用了监听者模式。程序员也可能在不知不觉中就运用了这个模式。
解决Android studio中找不到so文件的问题:java.lang.UnsatisfiedLinkError 表示我们不编译jni代码,直接从libs里面复制so库 文件路径:app\build.gradle android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.example.rust" minSdkVersion 21 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jni.srcDirs = [] jniLibs.srcDirs = ['libs'] } } }
Android Studio2.1 Run APP时,遇到错误 Error: Execution failed for task ':app:clean'. Unable to delete file 关闭AS,kill掉Java进程,打开资源管理器找到相应文件,仍旧无法删除这个文件。下载安装lockhunter,发现是金山杀毒软件占用着。 关闭金山毒霸仍旧无法删除文件,卸载金山毒霸后,可以删除文件。并能正常Run APP。
迭代器 Iterator 2016-5-7 可以这样说,迭代器统一了对容器的访问方式。 考虑这样的情景:原本是对着List编码,但是后来发现需要把相同的代码用于Set。我们需要一种不关心容器类型 而能够通用的容器访问方法。 Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。 迭代器是一个对象,它的工作是遍历并选中序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。 能将遍历序列的操作与序列底层的机构分离。 而且,创建迭代器的代价很小。 List<Integer> list = new LinkedList<>(); for (int i = 1; i < 6; i++) { list.add(i); } Iterator iterator = list.iterator(); System.out.println("iterator 本身: " + iterator); System.out.println(iterator.next()); while (iterator.hasNext()) { System.out.print(iterator.next() + " "); } 输出: iterator 本身: java.util.LinkedList$ListItr@1540e19d 1 2 3 4 5
Android-重新包装Toast,自定义背景 2016-4-27 Android L 算是包装了一个自己使用的小工具。 使用Toast的目的是弹一个提示框。先看一下Toast.makeText方法。 Toast.makeText(getApplicationContext(), this, "弹出一个Toast", Toast.LENGTH_SHORT).show(); 使用了Android自己的一个layout,然后把传入的text放到layout的TextView中。 public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast result = new Toast(context); LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); result.mNextView = v; result.mDuration = duration; return result; } 我们想自定义个Toast背景,并且调用方法和原来的Toast类似。 观察一下上面的Toast,使用的是Android里面的View。在这一步把View改成我们自己的即可达到目的。 新建文件ToastCustom.java public class ToastCustom { public static final int LENGTH_SHORT = Toast.LENGTH_SHORT; public static final int LENGTH_LONG = Toast.LENGTH_LONG; Toast toast; Context mContext; TextView toastTextField; public ToastCustom(Context context, Activity activity) { mContext = context; toast = new Toast(mContext); toast.setGravity(Gravity.BOTTOM, 0, 260);// 位置会比原来的Toast偏上一些 View toastRoot = activity.getLayoutInflater().inflate(R.layout.toast_view, null); toastTextField = (TextView) toastRoot.findViewById(R.id.toast_text); toast.setView(toastRoot); } public void setDuration(int d) { toast.setDuration(d); } public void setText(String t) { toastTextField.setText(t); } public static ToastCustom makeText(Context context, Activity activity, String text, int duration) { ToastCustom toastCustom = new ToastCustom(context, activity); toastCustom.setText(text); toastCustom.setDuration(duration); return toastCustom; } public void show() { toast.show(); } } 新建一个layout toast_view.xml,里面的style自己定义 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/toast_text" style="@style/TextToast" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="10dp" android:layout_marginTop="10dp" android:background="@drawable/shape_toast" android:textColor="@color/white" /> </LinearLayout> 调用方式和Toast一样: ToastCustom.makeText(getApplicationContext(), this, "弹出自定义背景Toast", ToastCustom.LENGTH_SHORT).show(); 之后就可以很方便地使用这个自定义背景的Toast
win7 旗舰版,从github上pull代码时,git bash命令出现错误 Administrator@rust-PC /g/rust_proj/cardslib (master) $ git --version git version 2.8.0.windows.1 Administrator@rust-PC /g/rust_proj/cardslib (master) $ git pull https://github.com/gabrielemariotti/cardslib.git fatal: I don't handle protocol 'https' 解决办法: Administrator@rust-PC /g/rust_proj/cardslib (master) $ git pull 'https://github.com/gabrielemariotti/cardslib.git' git version 2.6.4.windows.1 下可使用双引号 故障原因: 参见: http://stackoverflow.com/questions/30474447/git-fatal-i-dont-handle-protocol-http 在 git clone 和 http://... 之间看起来是一个空格,但它实际上是一个特殊的Unicode字符 删去这个字符,输入真正的空格后,命令可以使用了。 真正的命令应该是这样的: vi t.txt # copy+paste the line python open('t.txt').read() git clone \xe2\x80\x8b\xe2\x80\x8bhttp://...
Android 应用退到后台 2016-4-21 10:29:26 Android L moveTaskToBack(boolean nonRoot) 把包含这个Activity的任务转到后台。并不是finish。 传入Boolean参数:如果是false,在这个Activity是任务的根Activity时,方法才会起效。 传入true,任务中任意Activity都会起效。 /** * Move the task containing this activity to the back of the activity * stack. The activity's order within the task is unchanged. * * @param nonRoot If false then this only works if the activity is the root * of a task; if true it will work for any activity in * a task. * * @return If the task was moved (or it was already at the * back) true is returned, else false. */ public boolean moveTaskToBack(boolean nonRoot) { try { return ActivityManagerNative.getDefault().moveActivityTaskToBack( mToken, nonRoot); } catch (RemoteException e) { // Empty } return false; } 我们可以监听菜单键,按菜单键把APP退到后台: @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU) { moveTaskToBack(true);// 点击菜单键即转入后台,vivo X6Plus Android5.1也适用 return true; } } 点击返回键,退出当前Activity 点击返回键,执行onBackPressed(),最后会调用finish()。但是进程并没有被杀死。 Activity会进入onPause()。在合适的时机,Activity会进入onResume(),恢复状态。 /** * Take care of popping the fragment back stack or finishing the activity * as appropriate. */ public void onBackPressed() { if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) { supportFinishAfterTransition(); } } /** * Reverses the Activity Scene entry Transition and triggers the calling Activity * to reverse its exit Transition. When the exit Transition completes, * {@link #finish()} is called. If no entry Transition was used, finish() is called * immediately and the Activity exit Transition is run. * * <p>On Android 4.4 or lower, this method only finishes the Activity with no * special exit transition.</p> */ public void supportFinishAfterTransition() { ActivityCompat.finishAfterTransition(this); } /** * Reverses the Activity Scene entry Transition and triggers the calling Activity * to reverse its exit Transition. When the exit Transition completes, * {@link Activity#finish()} is called. If no entry Transition was used, finish() is called * immediately and the Activity exit Transition is run. * * <p>On Android 4.4 or lower, this method only finishes the Activity with no * special exit transition.</p> */ public static void finishAfterTransition(Activity activity) { if (Build.VERSION.SDK_INT >= 21) { ActivityCompat21.finishAfterTransition(activity); } else { activity.finish(); } }
最近打log的时候用到了字符串的格式化。 Java中String格式化和C语言的很类似。把情况都列出来,以后好查询。 public static void main(String[] args) { System.out.println(String.format("Hi, %s", "Rust Fisher")); // Hi, Rust Fisher System.out.println(String.format("%s: Hi, %s %s %s", "Lei Jun", "I", "am", "OK")); // Lei Jun: Hi, I am OK System.out.println(String.format("输出单个字符:%c", 'A')); // 输出单个字符:A System.out.printf("输出条件判断: 1>2 :%b %n", 1 > 2); // 输出条件判断: 1>2 :false System.out.printf("输出计算结果:100的一半是:%d %n", 100 / 2); // 输出计算结果:100的一半是:50 System.out.printf("输出float型:100元的书打8.5折扣是:%f 元%n", 100 * 0.85); // 输出float型:100元的书打8.5折扣是:85.000000 元 System.out.printf("输出进制转换:100的16进制数是:%x %n", 100); // 输出进制转换:100的16进制数是:64 System.out.printf("输出进制转换:100的8进制数是:%o %n", 100); // 输出进制转换:100的8进制数是:144 System.out.printf("输出结果进制转换:50 * 0.85的16进制数是:%a %n", 50 * 0.85); // 输出结果进制转换:50 * 0.85的16进制数是:0x1.54p5 System.out.printf("指数表示:%e %n", 100 * 0.85); // 指数表示:8.500000e+01 System.out.printf("上面价格的指数和浮点数结果的长度较短的是:%g %n", 50 * 0.85); // 上面价格的指数和浮点数结果的长度较短的是:42.5000 System.out.printf("百分比表示 %d%% %n", 85); // 百分比表示 85% System.out.printf("字母A的散列码是:%h %n", 'A'); // 字母A的散列码是:41 System.out.println("--------------------"); System.out.println(String.format("格式参数$的使用:%1$d,%2$s", 99, "abc")); // 格式参数$的使用:99,abc System.out.printf("显示正负数的符号:%+d与%+d %d %n", 99, -99, -99); // 显示正负数的符号:+99与-99 -99 System.out.printf("补O使用:%03d%n", 7); // 补O使用:007 System.out.printf("空格使用 Tab键的效果是:% 8d%n", 7); // 空格使用 Tab键的效果是: 7 System.out.printf(".使用 整数分组的效果是:%,d%n", 9989997); // .使用 整数分组的效果是:9,989,997 System.out.printf("空格和小数点后面个数 一本书的价格是:% 10.5f元%n", 49.8); // 空格和小数点后面个数 一本书的价格是: 49.80000元 System.out.println("--------------------"); Date date = new Date(); System.out.printf("c的使用 全部日期和时间信息:%tc%n", date); // c的使用 全部日期和时间信息:星期三 四月 20 21:03:57 CST 2016 System.out.printf("f的使用 年-月-日格式:%tF%n", date); // f的使用 年-月-日格式:2016-04-20 System.out.printf("d的使用 月/日/年格式:%tD%n", date); // d的使用 月/日/年格式:04/20/16 System.out.printf("r的使用 HH:MM:SS PM格式(12时制):%tr%n", date); // r的使用 HH:MM:SS PM格式(12时制):09:03:57 下午 System.out.printf("t的使用 HH:MM:SS格式(24时制):%tT%n", date); // t的使用 HH:MM:SS格式(24时制):21:03:57 System.out.printf("R的使用 HH:MM格式(24时制):%tR", date); // R的使用 HH:MM格式(24时制):21:03 System.out.println("\n------定义日期格式的转换符可以使日期通过指定的转换符生成新字符串-----"); // ------定义日期格式的转换符可以使日期通过指定的转换符生成新字符串----- Date date2 = new Date(); System.out.println(String.format(Locale.US, "英文月份简称:%tb", date2)); // 英文月份简称:Apr System.out.printf("本地月份简称:%tb%n", date2); // 本地月份简称:四月 System.out.println(String.format(Locale.US, "英文月份全称:%tB", date2)); // 英文月份全称:April System.out.printf("本地月份全称:%tB%n", date2); // 本地月份全称:四月 System.out.println(String.format(Locale.US, "英文星期的简称:%ta", date2)); // 英文星期的简称:Wed System.out.printf("本地星期的简称:%tA%n", date2); // 本地星期的简称:星期三 System.out.printf("年的前两位数字(不足两位前面补0):%tC%n", date2); // 年的前两位数字(不足两位前面补0):20 System.out.printf("年的后两位数字(不足两位前面补0):%ty%n", date2); // 年的后两位数字(不足两位前面补0):16 System.out.printf("一年中的天数(即年的第几天):%tj%n", date2); // 一年中的天数(即年的第几天):111 System.out.printf("两位数字的月份(不足两位前面补0):%tm%n", date2); // 两位数字的月份(不足两位前面补0):04 System.out.printf("两位数字的日(不足两位前面补0):%td%n", date2); // 两位数字的日(不足两位前面补0):20 System.out.printf("月份的日(前面不补0):%te \n", date2); // 月份的日(前面不补0) Date dateToTime = new Date(); System.out.printf("H的使用 2位数字24时制的小时(不足2位前面补0):%tH%n", dateToTime); // H的使用 2位数字24时制的小时(不足2位前面补0):21 System.out.printf("I的使用 2位数字12时制的小时(不足2位前面补0):%tI%n", dateToTime); // I的使用 2位数字12时制的小时(不足2位前面补0):09 System.out.printf("k的使用 2位数字24时制的小时(前面不补0):%tk%n", dateToTime); // k的使用 2位数字24时制的小时(前面不补0):21 System.out.printf("l的使用 2位数字12时制的小时(前面不补0):%tl%n", dateToTime); // l的使用 2位数字12时制的小时(前面不补0):9 System.out.printf("M的使用 2位数字的分钟(不足2位前面补0):%tM%n", dateToTime); // M的使用 2位数字的分钟(不足2位前面补0):03 System.out.printf("S的使用 2位数字的秒(不足2位前面补0):%tS%n", dateToTime); // S的使用 2位数字的秒(不足2位前面补0):57 System.out.printf("L的使用 3位数字的毫秒(不足3位前面补0):%tL%n", dateToTime); // L的使用 3位数字的毫秒(不足3位前面补0):220 System.out.printf("N的使用 9位数字的毫秒数(不足9位前面补0):%tN%n", dateToTime); // N的使用 9位数字的毫秒数(不足9位前面补0):220000000 System.out.println(String.format(Locale.US, "p的使用 小写字母的上午或下午标记(英):%tp", dateToTime)); // p的使用 小写字母的上午或下午标记(英):pm System.out.printf("小写字母的上午或下午标记(中):%tp%n", dateToTime); // 小写字母的上午或下午标记(中):下午 System.out.printf("z的使用 相对于GMT的RFC822时区的偏移量:%tz%n", dateToTime); // z的使用 相对于GMT的RFC822时区的偏移量:+0800 System.out.printf("Z的使用 时区缩写字符串:%tZ%n", dateToTime); // Z的使用 时区缩写字符串:CST System.out.printf("s的使用 1970-1-1 00:00:00 到现在所经过的秒数:%ts%n", dateToTime); // s的使用 1970-1-1 00:00:00 到现在所经过的秒数:1461157437 System.out.printf("Q的使用 1970-1-1 00:00:00 到现在所经过的毫秒数:%tQ%n", dateToTime); // Q的使用 1970-1-1 00:00:00 到现在所经过的毫秒数:1461157437220 } 在Android中TextView.setText时,如果单独传入一个数字,很容易出错 转成格式化字符串,或者直接转字符串即可 mHighValue.setText(String.format("%d", high));
Android Bluetooth Low Energy Android 低功耗蓝牙简介 2016-4-18 Android4.3(API 18)介绍了平台支持的低功耗蓝牙,app可用于发现设备,检索服务和读写特性(characteristics)。相比于传统蓝牙,低功耗蓝牙(BLE)消耗更少。它能够连接到外围的BLE设备,比如距离感应器、心率感应器、健康设备等等。 关键词和概念 Generic Attribute Profile (GATT) GATT模式是通过BLE连接发送接收“attributes”短数据的通用模式。目前所有的低功耗应用都基于GATT。 蓝牙SIG定义了许多的低功耗设备模式。一个配置(profile)是设备在特定应用下的工作方式。请注意,一个设备可以实现不止一个配置。比如,一个设备可以同时包含心率感应器和一个电量检测器。 Attribute Protocol (ATT) GATT建立在属性协议(ATT)之上。这也可以看为GATT/ATT。ATT有针对BLE设备的优化。ATT使用尽可能少的字节。每个特性(attributes)都由一个UUID来标示。UUID(Universally Unique Identifier)是一个标准128位的字符串ID,用于识别单一的信息。ATT以服务(services)和属性(characteristics)来传送特性(attributes)。 Characteristic 一个characteristic包含一个单独的值和0~n个描述值的descriptor。一个characteristic可以被当做是类的类型。 Descriptor 用于描述characteristic的值。比如一个描述器可能指定一个可读的描述语句。 Service service是characteristics的集合。比如你可以有一个service叫做“Heart Rate Monitor”,里面包含多个characteristic,比如“heart rate measurement”。 角色和职责 中心设备与外围设备。中心设备搜索寻找信号,外围设备发送信号。 GATT服务端与GATT客户端。这决定了2个设备建立连接后如何通信。 假设有一个Android手机和一个BLE设备。手机扮演中心角色,设备扮演外围角色。 手机和外设建立连接后,他们相互传送GATT元数据(Metadata)。由传送数据的类型来决定谁做主机。比如,外设想要报告传感器数据给手机,那么外设来做服务端。如果外设要从手机接收更新信息,那么手机来做服务端。 BLE Permissions <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 如果要声明APP仅适用于BLE设备,在manifest中写 <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> 上面的代码中true改为false,就是不支持BLE设备 // 检查这台设备是否支持BLE if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, “本机不支持BLE”, Toast.LENGTH_SHORT).show(); finish(); } 设置BLE 1.获取BluetoothAdapter 蓝牙app都需要BluetoothAdapter。 // Initializes Bluetooth adapter. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); 2.激活蓝牙 private BluetoothAdapter mBluetoothAdapter; ... // Ensures Bluetooth is available on the device and it is enabled. If not, // displays a dialog requesting user permission to enable Bluetooth. if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } 寻找BLE设备 DeviceScanActivity.java DeviceControlActivity.java BluetoothLeService.java DeviceScanActivity负责搜索BLE设备;选定设备后启动BluetoothLeService,并且所有BLE操作都在这里进行; DeviceControlActivity提供UI,从BluetoothLeService发来的信息在这里显示 调用startLeScan(),当然现在有替代的方法了 请注意: 一旦找到需要的设备,马上停止搜索 不要循环搜索,并设置一个时间限制。循环搜索很耗电。 以下代码是启动和停止搜索功能 /** * Activity for scanning and displaying available BLE devices. */ public class DeviceScanActivity extends ListActivity { private BluetoothAdapter mBluetoothAdapter; private boolean mScanning; private Handler mHandler; // Stops scanning after 10 seconds. private static final long SCAN_PERIOD = 10000; ... private void scanLeDevice(final boolean enable) { if (enable) { // Stops scanning after a pre-defined scan period. mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }, SCAN_PERIOD); mScanning = true; mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } ... } ... } 如果要搜索特定类型的外围设备,使用 startLeScan(UUID[], BluetoothAdapter.LeScanCallback) 提供一组特定的GATT services UUID对象 下面是BluetoothAdapter.LeScanCallback的实现 private LeDeviceListAdapter mLeDeviceListAdapter; ... // Device scan callback. private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { mLeDeviceListAdapter.addDevice(device); mLeDeviceListAdapter.notifyDataSetChanged(); } }); } }; 请注意,不能同时搜索BLE设备和传统蓝牙设备。 连接到GATT服务端 在GATT客户端进行这个操作 mBluetoothGatt = device.connectGatt(this, false, mGattCallback); // mGattCallback在上面写好了 下面是一个BLE服务示例 // A service that interacts with the BLE device via the Android BLE API. public class BluetoothLeService extends Service { private final static String TAG = BluetoothLeService.class.getSimpleName(); private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; private int mConnectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); // Various callback methods defined by the BLE API. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); } } @Override // New services discovered public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // Result of a characteristic read operation public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } ... }; ... } 这里值得注意的是,连接上之后不要马上进行读写characteristic的操作。要等onServicesDiscovered执行后再读写命令。 下面是发送广播的辅助方法 private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); } private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { final Intent intent = new Intent(action); // This is special handling for the Heart Rate Measurement profile. Data // parsing is carried out as per profile specifications. if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); } final int heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); } else { // For all other profiles, writes the data formatted in HEX. final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for(byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); } } sendBroadcast(intent); } 发送出来的广播在DeviceControlActivity中接收处理 // Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read or notification operations. private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { mConnected = true; updateConnectionState(R.string.connected); invalidateOptionsMenu(); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { mConnected = false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu(); clearUI(); } else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the // user interface. displayGattServices(mBluetoothLeService.getSupportedGattServices()); } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); } } }; 读取BLE属性 Android APP连接上BLE服务端后,就可以发现服务,并能读写属性值。 下面是一个读属性的例子,它将服务端所有的属性都列出来 public class DeviceControlActivity extends Activity { ... // Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView on the UI. private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources(). getString(R.string.unknown_service); String unknownCharaString = getResources(). getString(R.string.unknown_characteristic); ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); // Loops through available GATT Services. for (BluetoothGattService gattService : gattServices) { HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } ... } ... } 接收GATT提示 当设备上特定的characteristic改变时,app能够接收到提示 private BluetoothGatt mBluetoothGatt; BluetoothGattCharacteristic characteristic; boolean enabled; ... mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); ... BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); @Override // Characteristic notification public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } 关闭客户端APP 用完BLE设备后,调用close方法来关闭 public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; }
谈谈我对Java中CallBack的理解 转载自: http://www.cnblogs.com/codingmyworld/archive/2011/07/22/2113514.html CallBack是回调的意思,熟悉Windows编程的人对"回调函数"这四个字一定不会陌生,但是Java程序员对它可能就不太了解了。"回调函数"或者"回调方法"是软件设计与开发中一个非常重要的概念,掌握"回调函数"的思想对程序员来说(不管用哪种语言)是非常必要的。 那么什么是回调函数呢?我认为,回调函数就是预留给系统调用的函数,而且我们往往知道该函数被调用的时机。这里有两点需要注意:第一点,我们写回调函数不是给自己调用的,而是准备给系统在将来某一时刻调用的;第二点,我们应该知道系统在什么情形下会调用我们写的回调函数。 这里举个现实生活中"回调函数"的例子来帮助大家更好的理解。我们平时考试答题的第一件事是干嘛?没错,是写上学号和姓名。这里注意了,我们填写学号和姓名不是给自己看的(即该方法不是给自己调用的),而是给老师登记分数时看的(预留给系统将来调用),这其实就是一个回调的应用。 下面再来看一下Android中应用到"回调"的场景。 场景一: Button button = (Button)this.findViewById(R.id.button); button.setOnClickListener(new Button.OnClickListener() { //回调函数 @override public void onClick(View v) { buttonTextView.setText("按钮被点击了"); } }); 上面的代码给按钮加了一个事件监听器,这其实就是"回调"最常见的应用场景之一。我们自己不会显示地去调用onClick方法。用户触发了该按钮的点击事件后,它会由Android系统来自动调用。 场景二: @Override public void onCreate(Bundle saveInstanceState) { super.onCreate(saveInstanceState); // You code... } @Override public void onResume() { super.onResume(); // You code... } 上面的方法大家就更熟悉了,这是Android系统在Activity类中设置的回调函数,在Activity生命周期的不同阶段,Android系统会自动调用相应的方法(onCreate, onPause, onResume,onDestroy等等) 以上是两个Android中用到"回调"的场景,他们的代码实现可能不同,但是思想上是相近的,都是"回调"思想的体现。下面,我们在Java中分别模拟这两个场景。 首先模拟注册事件监听器。先写一个监听器接口 package com.listener; /** * 点击监听器接口 * @author CodingMyWorld * */ public interface MyOnClickListener { publicvoid onClick(); } 然后写一个我们自己的Button类 package com.listener; public class MyButton { private MyOnClickListener listener; /** * 设置具体点击监听器 * @param listener 点击监听器实现类 */ public void setOnClickListener(MyOnClickListener listener) { this.listener = listener; } /** * 按钮被点击 */ public void doClick() { listener.onClick(); } } 最后模拟Client端的注册监听器和触发点击操作。 package com.listener; public class Client { public static void main(String[] args) { MyButton button =new MyButton(); //注册监听器 button.setOnClickListener(new MyOnClickListener() { @Override public void onClick() { System.out.println("按钮被点击了"); } }); //模拟用户点击 button.doClick(); } } 以上就是"回调"思想在Java中事件监听的运用,我们再模拟第二个场景,"回调"在activity生命周期方法调用的体现。由于比较简单,我就不多做解释了,大家直接看代码。 package com.activity; public abstract class Activity { protected void onCreate() { System.out.println("创建准备~~~~~~~"); } protected void onDestroy() { System.out.println("销毁准备~~~~~~~~"); } } package com.activity; public class ConcreteActivity extends Activity { @Override protected void onCreate() { super.onCreate(); System.out.println("创建中!!!"); } @Override protected void onDestroy() { super.onDestroy(); System.out.println("销毁中!!!"); } } package com.activity; public class Client { public static void main(String[] args) { Activity activity =new ConcreteActivity(); activity.onCreate(); activity.onDestroy(); } }
Android Bluetooth 源码基于 Android L [TOC] Reference BluetoothAdapter 首先调用静态方法getDefaultAdapter()获取蓝牙适配器bluetoothadapter, 如果返回为空,则表明此设备不支持蓝牙。 代表本地蓝牙适配器。BluetoothAdapter 让你进行基础的蓝牙操作,比如初始化搜索设备,对已配对设备进行检索,根据一直MAC地址 实例化一个 BluetoothDevice,建立一个监听其他设备连接请求的 BluetoothServerSocket,启动对蓝牙LE设备的搜索。 这是使用蓝牙的起点。获得了本地适配器后,可以调用getBondedDevices()获得代表配对设备的 BluetoothDevice 对象startDiscovery()启动搜索蓝牙设备。或者建立一个BluetoothServerSocket , 调用listenUsingRfcommWithServiceRecord(String, UUID)来监听接入请求startLeScan(LeScanCallback) 搜索Bluetooth LE 设备 相应的源码 BluetoothAdapter.java (frameworks/base/core/java/android/bluetooth) /** * Get a handle to the default local Bluetooth adapter. * <p>Currently Android only supports one Bluetooth adapter, but the API * could be extended to support more. This will always return the default * adapter. * @return the default local adapter, or null if Bluetooth is not supported * on this hardware platform */ public static synchronized BluetoothAdapter getDefaultAdapter() { if (sAdapter == null) { IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE); if (b != null) { IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b); sAdapter = new BluetoothAdapter(managerService); } else { Log.e(TAG, "Bluetooth binder is null"); } } return sAdapter; } BluetoothSocket 一个连接上或连接中的socket 使用BluetoothServerSocket来创建一个监听的服务socket。 对于服务端,当BluetoothServerSocket接收了一个连接,会返回一个新的BluetoothSocket来管理这个连接。 对于客户端,使用一个单独的BluetoothSocket来初始化一个发送连接并管理这个连接。 蓝牙socket最普通的模式是RFCOMM,这是Android API支持的模式。 RFCOMM面向连接,使用流传输。也称为Serial Port Profile (SPP) 建立一个到已知蓝牙设备的BluetoothSocket,使用BluetoothDevice.createRfcommSocketToServiceRecord() 然后调用connect()来连接这个远程设备。调用这个方法会阻塞程序直到建立连接或者连接失败。 一旦socket连接上,不论初始化为客户端或者服务端,调用getInputStream()和getOutputStream()打开IO流来分别接收 输入流和输出流对象。流对象自动连接到socket BluetoothSocket是线程安全的。特别的是,close()会立刻关闭进行中的操作并关闭socket。 需要 BLUETOOTH 相关权限 BluetoothServerSocket 一个监听的蓝牙socket 在服务端,使用BluetoothServerSocket来建立一个监听的服务socket。 使用BluetoothAdapter.listenUsingRfcommWithServiceRecord()来建立一个监听接入连接的BluetoothServerSocket 然后调用accept()监听连接请求。这个调用会阻塞,直到建立连接,并返回一个管理连接的BluetoothSocket 获得了 BluetoothSocket,并且不再需要连接,可以调用close()来关闭掉 BluetoothServerSocket 关闭 BluetoothServerSocket 并不会关闭返回的 BluetoothSocket BluetoothServerSocket 是线程安全的,特别的是,close()会立刻关闭进行中的操作并关闭服务 socket。 BluetoothDevice 代表一个远程蓝牙设备。BluetoothDevice 能让你与其他设备建立连接,或者查询设备信息,比如名称,地址,类别和连接状态等。 这是蓝牙硬件地址的简单包装类。找个类的对象都说不可变的。这个类的操作会在远程蓝牙硬件地址上体现。 通过一个已知的MAC地址(可用BluetoothDevice来发现),或是通过BluetoothAdapter.getBondedDevices()返回的已连接设备 调用BluetoothAdapter.getRemoteDevice(String)来获得一个 BluetoothDevice。 然后就可以打开 BluetoothSocket 来与远程设备建立连接,调用createRfcommSocketToServiceRecord(UUID) API Guides 搜索其他蓝牙设备 检索匹配到的蓝牙设备 建立RFCOMM频道 通过发现服务来连接其他设备 管理多个连接 Setting Up Bluetooth 设置蓝牙 使用蓝牙通信前,确定设备支持蓝牙,并将蓝牙打开 1.获取 BluetoothAdapter 使用静态方法BluetoothAdapter.getDefaultAdapter()获取机器的蓝牙适配器;若返回null,则表示机器不支持蓝牙 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Device does not support Bluetooth } 2.激活蓝牙 检查蓝牙是否已经打开;若没打开,可以使用下面的方法打开蓝牙 if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } 会弹出一个对话框,请求用户打开蓝牙。如果成功打开,activity 的 onActivityResult() 会收到 RESULT_OK , 如果未打开, 则收到 RESULT_CANCELED 可以让应用监听 ACTION_STATE_CHANGED ,当蓝牙状态改变时会发出这个广播。这个广播包括蓝牙原先状态和现在状态,分别装在 EXTRA_PREVIOUS_STATE 和 EXTRA_STATE 里。 状态可能是 STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 和 STATE_OFF Finding Devices 寻找设备 使用 BluetoothAdapter,可以寻找远程蓝牙设备或检索匹配设备 搜索设备是搜索本地区域已开启的蓝牙设备程序。蓝牙设备有可见模式和不可见模式。如果一个设备是可发现的,它会相应搜索请求并 返回一些信息,比如设备名,类别,独立的MAC地址。利用这些信息,设备可以初始化一个到被发现设备的连接。 第一次与其他设备连接建立,会自动弹出一个配对请求给用户。成功配对后,设备的基本信息会被保存下来,并且可以被蓝牙API调用。 使用已知的远程设备的MAC地址,可以在不搜索设备的情况下建立连接。 配对和连接是不同的。配对表示两个设备知道对方的存在,有相互认证的key,能够与对方建立加密的连接。 连接意味着设备目前共享一个RFCOMM频道,并能相互发送数据。目前android蓝牙API要求设备先配对,再进行连接。 注意:Android设备并不是默认蓝牙可见的。用户可以在系统设置中让设备蓝牙可被搜索到。 Querying paired devices 检索已配对的设备 搜索设备前,可以调用getBondedDevices()检索一下已配对的设备。这会返回配对设备的BluetoothDevices集合。 例如,你可以检索配对设备并把每个设备信息存入 ArrayAdapter : Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); // If there are paired devices if (pairedDevices.size() > 0) { // Loop through paired devices for (BluetoothDevice device : pairedDevices) { // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } 只需要MAC地址,就能够用BluetoothDevice对象建立连接。 Discovering devices 搜索设备 调用startDiscovery()搜索设备。这个异步进程会立刻返回是否启动成功的boolean值。搜索进程通常是inquiry scan进行12秒, 接下来是每个发现设备的 page scan 。 你的应用必须注册一个广播接收器来监听 ACTION_FOUND ,接收发现的每个设备的信息。每发现一个设备,系统会发送 ACTION_FOUND 这个 Intent 带有 EXTRA_DEVICE 和 EXTRA_CLASS,里面分别包含 BluetoothDevice 和一个 BluetoothClass 例如,注册一个广播接收器来监听被发现设备: // Create a BroadcastReceiver for ACTION_FOUND private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // When discovery finds a device if (BluetoothDevice.ACTION_FOUND.equals(action)) { // Get the BluetoothDevice object from the Intent BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } }; // Register the BroadcastReceiver IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy 警告:搜索设备对于蓝牙适配器来说是一个耗费资源的进程。发现目标设备后,一定要在连接前调用cancelDiscovery()停止搜索。 不要在与设备连接时启动搜索。搜索设备会减小连接的带宽。 Enabling discoverability 激活设备蓝牙可见 如果想让本地设备对其他设备蓝牙可见,调用startActivityForResult(Intent, int),传入ACTION_REQUEST_DISCOVERABLE 这会请求激活系统设置。默认激活120秒。可以用EXTRA_DISCOVERABLE_DURATION来请求别的时间。应用可设最长时间是3600秒。 0表示设备永远可见。在0~3600外的数字会被设置为120秒。比如,将时间设置为300秒: Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent); 会显示一个对话框来请求用户。如果用户点击“Yes”,设备会变得蓝牙可见。activity会收到onActivityResult()回调,并传回 设置蓝牙可见时间的数值。如果用户点击“No”或出现错误,返回代码会是 RESULT_CANCELED 。 注意:如果设备未开启蓝牙,将设备蓝牙设为可见会自动打开蓝牙 设备会在允许时间内保持沉默。可以注册广播接收器来监听 ACTION_SCAN_MODE_CHANGED 。这个Intent会带着 EXTRA_SCAN_MODE 和 EXTRA_PREVIOUS_SCAN_MODE,分别是新的和旧的状态。可能的状态值会是: SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, 或 SCAN_MODE_NONE 如果要与远程设备连接,可以不激活设备可见。当应用想要建立服务socket获取接入时,必须打开设备可见。因为远程设备必须要发现 本地设备来建立连接。 Connecting Devices 连接设备 服务端设备和客户端设备以不同的方式获取 BluetoothSocket。连接建立时,服务端获取一个 BluetoothSocket。 客户端打开一个RFCOMM时会得到 BluetoothSocket 。 Connecting as a server 在连接中作为服务端 当你要连接2个设备,其中一个必须作为服务器并持有一个打开的 BluetoothServerSocket 。服务socket的目的是获取接入请求并 给已连上设备一个 BluetoothSocket 。当从 BluetoothServerSocket 获取到 BluetoothSocket,BluetoothServerSocket 可以关闭掉,除非你想接入更多连接。 调用listenUsingRfcommWithServiceRecord(String, UUID)来获得一个 BluetoothServerSocket 调用accept() 来监听接入请求 如果不需要接入更多连接,调用close() accept()不应该在UI线程中调用,因为它是阻塞式的。通常在应用中启动新的线程来操作BluetoothServerSocket 或 BluetoothSocket 在另一个线程调用BluetoothServerSocket (或 BluetoothSocket)的close()能跳出阻塞,并立刻返回 例子: private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } // If a connection was accepted if (socket != null) { // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); mmServerSocket.close(); break; } } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } } 上面这个例子只需要一个接入连接。连接建立并获得 BluetoothSocket 后,应用将这个 BluetoothSocket 发给单独的线程来处理 并关闭 BluetoothServerSocket 结束循环。 当accept()返回 BluetoothSocket,这个socket已经是连接上的了。因此不必调用connect()(客户端也一样) manageConnectedSocket()是一个虚构的方法,用来初始化传输数据的线程,在连接管理中来讨论它 监听接入连接结束后通常要立刻关闭 BluetoothServerSocket 。这个例子中,获取 BluetoothSocket 后立刻调用了close()。 可以在线程中写一个公共方法来关闭私有的 BluetoothSocket 。当需要停止监听服务socket时可以使用这个方法。 Connecting as a client 在连接中作为客户端 与远程设备(服务端)建立连接,先获取一个代表远程设备的 BluetoothDevice 对象。必须使用 BluetoothDevice 来获取 BluetoothSocket 并初始化连接。 基本流程: 调用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)获取BluetoothSocket 调用connect()来初始化连接 注意:在调用connect()时必须确保设备不在搜索进行中。在搜索设备时,连接尝试会变慢并且很容易失败。 private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null; mmDevice = device; // Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mBluetoothAdapter.cancelDiscovery(); try { // Connect the device through the socket. This will block // until it succeeds or throws an exception mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and get out try { mmSocket.close(); } catch (IOException closeException) { } return; } // Do work to manage the connection (in a separate thread) manageConnectedSocket(mmSocket); } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } } 建立连接前,调用cancelDiscovery()。 manageConnectedSocket()是一个虚构的方法,在连接管理中讨论它。 使用完BluetoothSocket,一定要调用close()来结束 Managing a Connection 成功连接2个或更多的设备后,每个设备有一个 BluetoothSocket 。设备直接可以共享数据。使用BluetoothSocket传输任意数据 获取处理传输的 InputStream 和 OutputStream,分别调用 getInputStream() 和 getOutputStream() 调用read(byte[]) 和 write(byte[]) 来读写数据 线程的主循环中应该用于专门从InputStream中读数据。线程中要有专门的public方法来写数据到OutputStream private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; // Get the input and output streams, using temp objects because // member streams are final try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; // buffer store for the stream int bytes; // bytes returned from read() // Keep listening to the InputStream until an exception occurs while (true) { try { // Read from the InputStream bytes = mmInStream.read(buffer); // Send the obtained bytes to the UI activity mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (IOException e) { break; } } } /* Call this from the main activity to send data to the remote device */ public void write(byte[] bytes) { try { mmOutStream.write(bytes); } catch (IOException e) { } } /* Call this from the main activity to shutdown the connection */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } } 构造方法需要数据流,一旦执行,线程会等InputStream中传来的数据。当read(byte[])返回数据流,通过handler将数据送往 主activity。然后等待数据流中更多的字节 向外发送数据调用write() 线程的cancel()方法很重要。完成了蓝牙连接的所有操作后,应当cancel掉
使用 FragmentTabHost 与 Fragment 制作页面切换效果 API 19 TabHost已经不建议使用了。用 FragmentTabHost 来代替TabHost。实际上 FragmentTabHost 继承自 TabHost 效果图: 主文件是FragmentTabHostDemo.java 继承自FragmentActivity; 设置3个底部标签,自定义了标签切换时的标签变化; 添加标签页有多种方式,每个标签页对应一个fragment 每次切换fragment,都会调用fragment的onCreateView()和onResume()方法; v4包使用getSupportFragmentManager(); 动态加载fragment,不用在xml中注册; 其他的大体和TabHost一样;比如xml文件中的id要用android指定的id; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTabHost; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TabHost; import android.widget.TextView; import com.rust.aboutview.fragment.TabFragment1; import com.rust.aboutview.fragment.TabFragment2; import com.rust.aboutview.fragment.TabFragment3; import java.util.HashMap; public class FragmentTabHostDemo extends FragmentActivity { public static final int COLOR_GRAY_01 = 0xFFADADAD; //自定义的颜色 public static final int COLOR_GREEN_01 = 0xFF73BF00; public static final String TAB1 = "tab1"; public static final String TAB2 = "tab2"; public static final String TAB3 = "tab3"; public static final String TABS[] = {TAB1, TAB2, TAB3}; public static HashMap<String, Integer> mTabMap; public static FragmentTabHost mTabHost; LayoutInflater mLayoutInflater; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment_tab_host); mTabMap = new HashMap<>(); mTabMap.put(TAB1, 0); mTabMap.put(TAB2, 1); mTabMap.put(TAB3, 2); mLayoutInflater = LayoutInflater.from(getApplicationContext()); mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost); mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent); mTabHost.getTabWidget().setMinimumHeight(120);// 设置tab的高度 mTabHost.getTabWidget().setDividerDrawable(null); TabHost.TabSpec tabSpec = mTabHost.newTabSpec(TABS[0]); View tabView1 = mLayoutInflater.inflate(R.layout.tab_item, null); final ImageView tabImage1 = (ImageView) tabView1.findViewById(R.id.tab_image); final TextView tabText1 = (TextView) tabView1.findViewById(R.id.tab_text); tabImage1.setImageResource(R.drawable.a4a); tabText1.setText(getString(R.string.tab_label_1)); tabText1.setTextColor(COLOR_GREEN_01); tabSpec.setIndicator(tabView1); mTabHost.addTab(tabSpec, TabFragment1.class, null); View tabView2 = mLayoutInflater.inflate(R.layout.tab_item, null); final ImageView tabImage2 = (ImageView) tabView2.findViewById(R.id.tab_image); tabImage2.setImageResource(R.drawable.a49); final TextView tabText2 = (TextView) tabView2.findViewById(R.id.tab_text); tabText2.setText(getString(R.string.tab_label_2)); mTabHost.addTab(mTabHost.newTabSpec(TABS[1]).setIndicator(tabView2), TabFragment2.class, null); View tabView3 = mLayoutInflater.inflate(R.layout.tab_item, null); final ImageView tabImage3 = (ImageView) tabView3.findViewById(R.id.tab_image); tabImage3.setImageResource(R.drawable.a49); final TextView tabText3 = (TextView) tabView3.findViewById(R.id.tab_text); tabText3.setText(getString(R.string.tab_label_3)); mTabHost.addTab(mTabHost.newTabSpec(TABS[2]) .setIndicator(tabView3), TabFragment3.class, null); mTabHost.setCurrentTab(0); mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() { @Override public void onTabChanged(String tabId) { int child = mTabMap.get(tabId); tabImage1.setImageResource(R.drawable.a49); tabImage2.setImageResource(R.drawable.a49); tabImage3.setImageResource(R.drawable.a49); tabText1.setTextColor(COLOR_GRAY_01); tabText2.setTextColor(COLOR_GRAY_01); tabText3.setTextColor(COLOR_GRAY_01); switch (child) { case 0: tabImage1.setImageResource(R.drawable.a4a); tabText1.setTextColor(COLOR_GREEN_01); break; case 1: tabImage2.setImageResource(R.drawable.a4a); tabText2.setTextColor(COLOR_GREEN_01); break; case 2: tabImage3.setImageResource(R.drawable.a4a); tabText3.setTextColor(COLOR_GREEN_01); break; } } }); } } activity_fragment_tab_host.xml,使用FragmentTabHost; 标签放在页面底部;注意这里的id,以及layout的宽高设置 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <FrameLayout android:id="@+id/realtabcontent" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" /> <android.support.v4.app.FragmentTabHost android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@color/colorYellow01"> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0" /> </android.support.v4.app.FragmentTabHost> </LinearLayout> 因为切换标签时会重载fragment,可以在fragment中判断一下,已经加载过的,不需要重新加载; TabFragment1.java 中定义了一个rootView public class TabFragment1 extends Fragment { private View rootView;// cache fragment view TextView centerTV; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d("rust", "TabFragment1 onCreateView"); if (rootView == null) { rootView = inflater.inflate(R.layout.fragment_tab1, null); } ViewGroup parent = (ViewGroup) rootView.getParent(); // if root view had a parent, remove it. if (parent != null) { parent.removeView(rootView); } centerTV = (TextView) rootView.findViewById(R.id.center_tv); centerTV.setOnClickListener(new View.OnClickListener() { // @Override public void onClick(View v) { centerTV.setText(String.format("%s","Tab1 clicked")); centerTV.setTextColor(Color.BLACK); } }); return rootView; } @Override public void onResume() { super.onResume(); Log.d("rust", "TabFragment1 onResume"); } } 已点击的效果图:
Android - TabHost 与 Fragment 制作页面切换效果 Android API 19 , API 23 三个标签页置于顶端 效果图: 在文件BoardTabHost.java中定义页面切换的效果;切换页面时,当前页面滑出,目标页面滑入。这是2个不同的动画 设定动画时要区分对待 import android.content.Context; import android.util.AttributeSet; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.TabHost; public class BoardTabHost extends TabHost { private int currentTab = 0; int duration = 1000;// ms; the bigger the slower public BoardTabHost(Context context) { super(context); } public BoardTabHost(Context context, AttributeSet attr) { super(context, attr); } @Override public void setCurrentTab(int index) { // we need two animation here: first one is fading animation, 2nd one is coming animation // translateAnimation of fading fragment if (index > currentTab) {// fly right to left and leave the screen TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_SELF/* fromXType */, 0f/* fromXValue */, Animation.RELATIVE_TO_SELF/* toXType */, -1.0f/* toXValue */, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f ); translateAnimation.setDuration(duration); getCurrentView().startAnimation(translateAnimation); } else if (index < currentTab) {// fly left to right TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f ); translateAnimation.setDuration(duration); getCurrentView().startAnimation(translateAnimation); } super.setCurrentTab(index);// the current tab is index now // translateAnimation of adding fragment if (index > currentTab) { TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, 1.0f,/* fly into screen */ Animation.RELATIVE_TO_PARENT, 0f, /* screen location */ Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0f ); translateAnimation.setDuration(duration); getCurrentView().startAnimation(translateAnimation); } else if (index < currentTab) { TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 0f ); translateAnimation.setDuration(duration); getCurrentView().startAnimation(translateAnimation); } currentTab = index; } } 对应的布局文件activity_board.xml 使用BoardTabHost,装载一个竖直的LinearLayout;上面是TabWidget,装载标签;后面是fragment的FrameLayout 可以看到这里有3个fragment,待会在activity中也设置3个标签 <?xml version="1.0" encoding="utf-8"?> <com.rust.tabhostdemo.BoardTabHost android:id="@android:id/tabhost" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.rust.tabhostdemo.BoardActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TabWidget android:id="@android:id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content"/> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/fragment_tab1" android:name="com.rust.tabhostdemo.TabFragment1" android:layout_width="match_parent" android:layout_height="match_parent"/> <fragment android:id="@+id/fragment_tab2" android:name="com.rust.tabhostdemo.TabFragment2" android:layout_width="match_parent" android:layout_height="match_parent"/> <fragment android:id="@+id/fragment_tab3" android:name="com.rust.tabhostdemo.TabFragment3" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout> </LinearLayout> </com.rust.tabhostdemo.BoardTabHost> 值得一提的是,这里的id要用android指定的id; 比如@android:id/tabhost,@android:id/tabcontent,@android:id/tabs;否则系统找不到对应控件而报错 BoardActivity.java中设置了3个标签页,并指定了标签对应的fragment import android.support.v4.app.FragmentActivity; import android.os.Bundle; public class BoardActivity extends FragmentActivity { public static final String TAB1 = "tab1"; public static final String TAB2 = "tab2"; public static final String TAB3 = "tab3"; public static BoardTabHost boardTabHost; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_board); boardTabHost = (BoardTabHost) findViewById(android.R.id.tabhost); boardTabHost.setup(); boardTabHost.addTab(boardTabHost.newTabSpec(TAB1).setIndicator(getString(R.string.tab1_name)) .setContent(R.id.fragment_tab1)); boardTabHost.addTab(boardTabHost.newTabSpec(TAB2).setIndicator(getString(R.string.tab2_name)) .setContent(R.id.fragment_tab2)); boardTabHost.addTab(boardTabHost.newTabSpec(TAB3).setIndicator(getString(R.string.tab3_name)) .setContent(R.id.fragment_tab3)); boardTabHost.setCurrentTab(0); } } 主要文件目录: ── layout ├── activity_board.xml ├── fragment_tab1.xml ├── fragment_tab2.xml └── fragment_tab3.xml ── tabhostdemo ├── BoardActivity.java ├── BoardTabHost.java ├── TabFragment1.java ├── TabFragment2.java └── TabFragment3.java
Android Daydream 互动屏保 API19 API23 Create:2016-03-01 继承DreamService来实现一个自定义屏保 Dreams是当充电的设备空闲,或者插入底座时显示的互动屏保。在展览或陈列时,Dreams为APP提供一个定制的展示方式。 DreamService的生命周期 1.onAttachedToWindow() 初始化设置,在这里可以调用 setContentView() 2.onDreamingStarted() 互动屏保已经启动,这里可以开始播放动画或者其他操作 3.onDreamingStopped() 在停止 onDreamingStarted() 里启动的东西 4.onDetachedFromWindow() 在这里回收前面调用的资源(比如 handlers 和 listeners) 另外,onCreate 和 onDestroy 也会被调用。但要复写上面的几个方法来执行初始化和销毁操作。 manifest 声明 为了能让系统调用,你的 DreamService 应该在 APP 的 manifest 中注册: <service android:name=".MyDream" android:exported="true" android:icon="@drawable/my_icon" android:label="@string/my_dream_label" > <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <!-- Point to additional information for this dream (optional) --> <meta-data android:name="android.service.dream" android:resource="@xml/my_dream" /> </service> 如果填写了 <meta-data> 元素,dream的附加信息就被指定在XML文件的 <dream> 元素中。 通常提供的附加信息是对互动屏保的自定义设置,指向一个自己写的Activity。 比如:res/xml/my_dream.xml <dream xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.example.app/.MyDreamSettingsActivity" /> 这样在Settings-Display-Daydream-你的Daydream选项右边会出现一个设置图标。点击此图标可打开指定的activity。 当目标api>=21,必须在manifest中申请BIND_DREAM_SERVICE权限,比如: <service android:name=".MyDream" android:exported="true" android:icon="@drawable/my_icon" android:label="@string/my_dream_label" android:permission="android.permission.BIND_DREAM_SERVICE"> <intent-filter> <action android:name=”android.service.dreams.DreamService” /> <category android:name=”android.intent.category.DEFAULT” /> </intent-filter> </service> 如果不申请权限,这个互动屏保将无法启动并有类似报错: system_process W/ActivityManager: Unable to start service Intent { act=android.service.dreams.DreamService flg=0x800000 cmp=com.google.android.deskclock/com.android.deskclock.Screensaver } U=0: not found system_process E/DreamController: Unable to bind dream service: Intent { act=android.service.dreams.DreamService flg=0x800000 cmp=com.google.android.deskclock/com.android.deskclock.Screensaver } system_process I/DreamController: Stopping dream: name=ComponentInfo{com.google.android.deskclock/com.android.deskclock.Screensaver}, isTest=false, canDoze=false, userId=0 demo AndroidManifest.xml 注册这个service;里面指定的图标和标题都显示在设置中 <service android:name="com.rust.service.MyDayDream" android:exported="true" android:icon="@drawable/littleboygreen_x128" android:label="@string/my_day_dream_label" android:permission="android.permission.BIND_DREAM_SERVICE"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> MyDayDream.java 互动屏保的定义 package com.rust.service; import android.service.dreams.DreamService; import com.rust.aboutview.R; public class MyDayDream extends DreamService { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); // Exit dream upon user touch setInteractive(false); // Hide system UI setFullscreen(true); // Set the dream layout setContentView(R.layout.my_day_dream); } } my_day_dream.xml 互动屏保的布局文件;只有一行字 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_day_dream_label" android:textColor="@color/colorRed" android:textSize="30sp" /> </LinearLayout> 在Settings-Display-Daydream中可以找到新增的选项
String to Integer (atoi) Implement atoi to convert a string to an integer. 【函数说明】atoi() 函数会扫描 str 字符串,跳过前面的空白字符(例如空格,tab缩进等),直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。 【返回值】返回转换后的整型数;如果 str 不能转换成 int 或者 str 为空字符串,那么将返回 0。如果超出Integer的范围,将会返回Integer最大值或者最小值。 【处理思路】按照函数说明来一步步处理。首先判断输入是否为null。然后使用trim()函数删掉空格。判断是否有正负号,做一个标记。返回的是整形数,可以使用double来暂存结果。按位来计算出结果。如果遇到非数字字符,则返回当前结果。加上前面的正负号。结果若超出了整形范围,则返回最大或最小值。最后返回处理结果。 /* * 8. String to Integer (atoi) * Implement atoi to convert a string to an integer. * 2016-2-20 */ public static int myAtoi(String str) { if (str == null || str.length() < 1) { return 0; } str = str.trim(); // kill add white spaces int i = 0; // index of str char flag = '+'; // default positive if (str.charAt(0) == '-') { flag = '-'; i++; } else if (str.charAt(0) == '+') { i++; } double res = 0; // abandon the non-digit char; calculate the result while (str.length() > i && str.charAt(i) >= '0' && str.charAt(i) <= '9') { res = res * 10 + str.charAt(i) - '0'; i++; } if (flag == '-') res = -1 * res; if (res > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } else if (res < Integer.MIN_VALUE) { return Integer.MIN_VALUE; } return (int) res; }
AIDL(Android Interface Definition Language) 程序员可以利用AIDL自定义编程接口,在客户端和服务端之间实现进程间通信(IPC)。在Android平台上,一个进程通常不能访问另外一个进程的内存空间,因此,Android平台将这些跨进程访问的对象分解成操作系统能够识别的简单对象。并为跨应用访问而特殊编排和整理这些对象。用于编排和整理这些对象的代码编写起来十分冗长,所以Android的AIDL提供了相关工具来自动生成这些代码。 例子:创建两个apk,一个作为服务提供方,一个作为AIDL服务调用方。 android studio AIDL服务方代码 一共4步 1.先进入服务方的工程,在com.rust.aidl包内创建IMyService.aidl文件 // IMyService.aidl package com.rust.aidl; // Declare any non-default types here with import statements interface IMyService { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); String helloAndroidAIDL(String name);// 此次使用的方法 } 2.在com.rust.service包内创建MyService.java文件;有一个内部类MyServiceImpl实现接口的功能 package com.rust.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.support.annotation.Nullable; import android.util.Log; import com.rust.aidl.IMyService; public class MyService extends Service { public class MyServiceImpl extends IMyService.Stub { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } public String helloAndroidAIDL(String name) throws RemoteException { Log.d("aidl", "helloAndroidAIDL heard from : " + name); return "Rust: Service01 return value successfully!"; } } @Nullable @Override public IBinder onBind(Intent intent) { return new MyServiceImpl();// 返回内部类实例 } } 3.实现了MyService类后,对此AIDL服务进行配置;在AndroidManifest.xml文件中配置 <service android:name="com.rust.service.MyService"> <intent-filter> <action android:name="com.rust.aidl.IMyService" /> </intent-filter> </service> service写实现类MyService;action里面写上AIDL文件 4.发布运行此apk AIDL调用方代码 建立(或进入)AIDL调用方的工程,这里是MyAIDLTest工程。有如下3个步骤: 1.将AIDL服务端生成的Java文件复制到调用方工程里,尽量保持这个Java文件的路径与服务端的一致,便于识别 2.写代码绑定服务,获取AIDL服务对象 3.通过AIDL服务对象完成AIDL接口调用 本例中,生成的Java文件路径为:服务端/app/build/generated/source/aidl/debug/com/rust/aidl/IMyService.java 将其复制到调用方工程下:MyAIDLTest/app/src/main/java/com/rust/aidl/IMyService.java 编写调用方MainActivity.java代码 package rust.myaidltest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.rust.aidl.IMyService; public class MainActivity extends AppCompatActivity { Button aidlBtn; IMyService myService;// 服务 String appName = "unknown"; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { myService = IMyService.Stub.asInterface(service);// 获取服务对象 aidlBtn.setEnabled(true); }// 连接服务 @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); aidlBtn = (Button) findViewById(R.id.aidl_1_btn); appName = getPackageName(); // 我们没办法在构造Intent的时候就显式声明. Intent intent = new Intent("com.rust.aidl.IMyService"); // 既然没有办法构建有效的component,那么给它设置一个包名也可以生效的 intent.setPackage("com.rust.aboutview");// the service package // 绑定服务,可设置或触发一些特定的事件 bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); aidlBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { // AIDL服务调用代码如下: String msg = myService.helloAndroidAIDL(appName); Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } } }); } } 效果 点击调用端的按钮,弹出Toast:Rust: Service01 return value successfully! 服务端apk打印log:helloAndroidAIDL heard from : rust.myaidltest 其中,rust.myaidltest就是调用端传入的自身的包名 服务端更新后,如果aidl文件没改动,不需要更新生成的Java文件 如果服务端apk被卸载,调用端使用此服务时会出错
自定义控件 - 圈圈 Android L; Android Studio 效果:能够自定义圆圈半径和位置;设定点击效果;改变背景颜色 下面是demo图 点击前: 点击后: 自定义控件一般要继承View;写出构造方法,并设定属性;复写onDraw方法 并在xml中配置一下 例子:OptionCircle.java CirclesActivity.java activity_circle_choose.xml 这个例子没有使用attrs.xml 控件 OptionCircle 这里继承的是ImageView;设定了多个属性,有半径,圆心位置,背景颜色和字体颜色等等 针对这些属性,开放set方法;方便设置属性;可以改变这些属性来做出一些动画效果 构造方法中预设几个属性,设置画笔,背景颜色和圆圈的半径onDraw方法中开始绘制控件;先画圆形,在圆形中心画上文字;文字中心定位需要特别计算一下 import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; public class OptionCircle extends ImageView { private final Paint paint; private final Context context; boolean clicked = false;// 是否被点击 boolean addBackground = false; int radius = -1; // 半径值初始化为-1 int centerOffsetX = 0;// 圆圈原点的偏移量x int centerOffsetY = 0;// 偏移量y int colorCircle; // 圆圈颜色 int colorBackground; // 背景填充颜色 int colorText; // 文字颜色 String textCircle = ""; public OptionCircle(Context context) { this(context, null); } public OptionCircle(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; this.paint = new Paint(); this.paint.setAntiAlias(true); this.paint.setStyle(Paint.Style.STROKE); colorCircle = Color.argb(205, 245, 2, 51);// 默认颜色 colorText = colorCircle; // 字体颜色默认与圈圈颜色保持一致 colorBackground = colorCircle;// 设定默认参数 } // 属性设置方法 public void setRadius(int r) { this.radius = r; } public void setCenterOffset(int x, int y) { this.centerOffsetX = x; this.centerOffsetY = y; } public void setColorCircle(int c) { this.colorCircle = c; } public void setColorText(int c) { this.colorText = c; } public void setColorBackground(int c) { this.colorBackground = c; } public void setText(String s) { this.textCircle = s; } public void setClicked(boolean clicked) { this.clicked = clicked; } public void setAddBackground(boolean add) { this.addBackground = add; } @Override protected void onDraw(Canvas canvas) { int center = getWidth() / 2;// 当前宽度的一半 int innerCircle = 86; // 默认半径为86 if (radius > 0) { innerCircle = dip2px(context, radius); // 如果没有另外设置半径,取半径86 } Drawable drawable = getDrawable(); if (addBackground) { } else { // 画圈圈;被点击后会变成实心的圈圈,默认是空心的 this.paint.setStyle(clicked ? Paint.Style.FILL : Paint.Style.STROKE); this.paint.setColor(clicked ? colorBackground : colorCircle); this.paint.setStrokeWidth(1.5f); canvas.drawCircle(center + centerOffsetX, center + centerOffsetY, innerCircle, this.paint);// 画圆圈时带上偏移量 } // 绘制文字 this.paint.setStyle(Paint.Style.FILL); this.paint.setStrokeWidth(1); this.paint.setTextSize(22); this.paint.setTypeface(Typeface.MONOSPACE);// 设置一系列文字属性 this.paint.setColor(clicked ? Color.WHITE : colorText); this.paint.setTextAlign(Paint.Align.CENTER);// 文字水平居中 Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); canvas.drawText(textCircle, center + centerOffsetX, center + centerOffsetY - (fontMetrics.top + fontMetrics.bottom) / 2, this.paint);// 设置文字竖直方向居中 super.onDraw(canvas); } /** * convert dp to px */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } } 配置 activity_circle_choose.xml 控件文件定义完毕,在activity_circle_choose.xml中配置一下 定义4个圈圈;center_circle定位在中心;circle_0是红色的;circle_1是绿色的;circle_2是洋红色的 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/top_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="5dp" android:text="@string/circles_top_title" android:textSize="26sp" /> <com.rust.aboutview.view.OptionCircle android:id="@+id/center_circle" android:layout_width="140dp" android:layout_height="140dp" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> <com.rust.aboutview.view.OptionCircle android:id="@+id/circle_0" android:layout_width="200dp" android:layout_height="200dp" android:layout_marginStart="130dp" android:layout_marginTop="53dp" /> <com.rust.aboutview.view.OptionCircle android:id="@+id/circle_1" android:layout_width="210dp" android:layout_height="210dp" android:layout_below="@+id/circle_0" android:layout_toEndOf="@+id/center_circle" /> <com.rust.aboutview.view.OptionCircle android:id="@+id/circle_2" android:layout_width="210dp" android:layout_height="210dp" android:layout_below="@id/center_circle" /> </RelativeLayout> 在 CirclesActivity.java 中使用圈圈 圈圈类OptionCircle.java已经开放了设置属性的方法,我们可以利用这些方法来调整圈圈的样式,比如半径,颜色,圆心偏移量 center_circle固定在屏幕中间不动 circle_0仿造一个放大缩小的效果,改变半径值即可实现 circle_1仿造一个浮动的效果,改变圆心偏移量来实现 circle_2仿造抖动效果,也是改变圆心偏移量 这些圈圈都可以自定义背景颜色 import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import com.rust.aboutview.view.OptionCircle; public class CirclesActivity extends Activity { public static final String TAG = "CirclesActivity"; public static final int circle0_r = 88; private static final int SLEEPING_PERIOD = 100; // 刷新UI间隔时间 private static final int UPDATE_ALL_CIRCLE = 99; int circleCenter_r; int circle1_r; boolean circle0Clicked = false; boolean circle1Clicked = false; OptionCircle centerCircle; OptionCircle circle0; OptionCircle circle1; OptionCircle circle2; CircleHandler handler = new CircleHandler(this); /** * Handler : 用于更新UI */ static class CircleHandler extends Handler { CirclesActivity activity; boolean zoomDir = true; boolean circle2Shaking = false; int r = circle0_r; int moveDir = 0; // 浮动方向 int circle1_x = 0;// 偏移量的值 int circle1_y = 0; int circle2_x = 0; int circle2ShakeTime = 0; int circle2Offsets[] = {10, 15, -6, 12, 0};// 抖动偏移量坐标 CircleHandler(CirclesActivity a) { activity = a; } @Override public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_ALL_CIRCLE: { if (zoomDir) {// 用简单的办法实现半径变化 r++; if (r >= 99) zoomDir = false; } else { r--; if (r <= circle0_r) zoomDir = true; } activity.circle0.invalidate(); activity.circle0.setRadius(r); calOffsetX();// 计算圆心偏移量 activity.circle1.invalidate(); activity.circle1.setCenterOffset(circle1_x, circle1_y); if (circle2Shaking) { if (circle2ShakeTime < circle2Offsets.length - 1) { circle2ShakeTime++; } else { circle2Shaking = false; circle2ShakeTime = 0; } activity.circle2.invalidate(); activity.circle2.setCenterOffset(circle2Offsets[circle2ShakeTime], 0); } } } } // 计算circle1圆心偏移量;共有4个浮动方向 private void calOffsetX() { if (moveDir == 0) { circle1_x--; circle1_y++; if (circle1_x <= -6) moveDir = 1; } if (moveDir == 1) { circle1_x++; circle1_y++; if (circle1_x >= 0) moveDir = 2; } if (moveDir == 2) { circle1_x++; circle1_y--; if (circle1_x >= 6) moveDir = 3; } if (moveDir == 3) { circle1_x--; circle1_y--; if (circle1_x <= 0) moveDir = 0; } } } class UpdateCircles implements Runnable { @Override public void run() { while (true) {// 配合Handler,循环刷新UI Message message = new Message(); message.what = UPDATE_ALL_CIRCLE; handler.sendEmptyMessage(message.what); try { Thread.sleep(SLEEPING_PERIOD); // 暂停 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_circle_choose); centerCircle = (OptionCircle) findViewById(R.id.center_circle); circle0 = (OptionCircle) findViewById(R.id.circle_0); circle1 = (OptionCircle) findViewById(R.id.circle_1); circle2 = (OptionCircle) findViewById(R.id.circle_2); circleCenter_r = 38; circle1_r = 45; // 设置圈圈的属性 centerCircle.setRadius(circleCenter_r); centerCircle.setColorText(Color.BLUE); centerCircle.setColorCircle(Color.BLUE); centerCircle.setText("点击圈圈"); circle0.setColorText(Color.RED); circle0.setRadius(circle0_r); circle0.setText("RED"); circle1.setColorCircle(Color.GREEN); circle1.setColorText(Color.GREEN); circle1.setText("Green"); circle1.setRadius(circle1_r); circle2.setColorCircle(getResources().getColor(R.color.colorMagenta)); circle2.setColorText(getResources().getColor(R.color.colorMagenta)); circle2.setText("Frozen!"); // 设定点击事件,可在这里改变控件的属性 circle0.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { circle0Clicked = !circle0Clicked; // 每次点击都取反 circle0.setClicked(circle0Clicked); } }); circle1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { circle1Clicked = !circle1Clicked; circle1.setColorBackground(Color.GREEN); circle1.setClicked(circle1Clicked); } }); circle2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { handler.circle2Shaking = true;// 颤抖吧! } }); Thread t = new Thread(new UpdateCircles()); t.start();// 开启子线程 } } 至此,圈圈demo结束。通过简单的计算,模拟出浮动,抖动,缩放的效果 以上的代码,复制粘贴进工程里就能使用。圆心移动的轨迹,用三角函数来计算会更好 这里继承的是ImageView,应该有办法在圈内动态添加背景Bitmap,效果更好看
Android 上层应用读写设备节点 Android L [TOC] 1. Android 设备节点 Android基于Linux内核。设备节点文件是设备驱动的逻辑文件,可以通过设备节点来访问设备驱动。 很多设备信息都可存储在节点中。apk可以访问节点,获取设备信息或状态。 2. framework中读取节点的例子 Android Settings 应用中给出了很多的设备信息,可以以此为入口;进一步可以找到 Build.java 例如获取设备的版本号,应用中直接可以调用 Build.DISPLAY 获得字符串 源码 Build.java (frameworks\base\core\java\android\os) public static final String PRODUCT = getString("ro.product.name"); ...... private static String getString(String property) { return SystemProperties.get(property, UNKNOWN); } 跳转到 SystemProperties.java (frameworks\base\core\java\android\os) 这个类不开放 // 调用 native_get ,获取节点;可以设定默认值 public static String get(String key, String def) { if (key.length() > PROP_NAME_MAX) { throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); } return native_get(key, def); } 3. 应用层读写节点 应用层中,一般都能够读取设备节点。对于写节点这个操作,需要更高的root权限。 读取设备节点 例如给设备新添加了节点,路径是 /sys/class/demo/version 可以adb shell进入机器,然后 cat /sys/class/demo/version;即可获得信息 也可以写成一个方法,如下: /** * 获取节点 */ private static String getString(String path) { String prop = "waiting";// 默认值 try { BufferedReader reader = new BufferedReader(new FileReader(path)); prop = reader.readLine(); } catch (IOException e) { e.printStackTrace(); } return prop; } 调用方法就是: getString("/sys/class/demo/version") 写设备节点 这里写节点的方法需要更高的权限,apk要放到源码中进行编译; 源码中编译apk的方法如同添加第三方apk的方法 AndroidManifest中添加: android:sharedUserId="android.uid.system" 写节点的代码 private static final String WAKE_PATH = "/sys/class/demo/wake"; ...... try { BufferedWriter bufWriter = null; bufWriter = new BufferedWriter(new FileWriter(WAKE_PATH)); bufWriter.write("1"); // 写操作 bufWriter.close(); Toast.makeText(getApplicationContext(), "功能已激活",Toast.LENGTH_SHORT).show(); Log.d(TAG,"功能已激活 angle " + getString(ANGLE_PATH)); } catch (IOException e) { e.printStackTrace(); Log.e(TAG,"can't write the " + WAKE_PATH); } 经过源码mm编译后,push到设备中可以查看效果 定时读取设备节点 需要被更新的View记得调用invalidate()方法 使用定时器与Handler来定时读取节点,并更新UI 重启定时器和取消定时器都封装成方法,便于调用 Timer mTimer; TimerTask mTimerTask; SensorHandler mHandler = new SensorHandler(this); /** * Handler : update value */ static class SensorHandler extends Handler { MainActivity mainActivity; SensorHandler(MainActivity activity) { mainActivity = activity; } @Override public void handleMessage(Message msg) { mainActivity.ultrasoundValue.setText(getString(ULTRASOUND_VALUE_PATH)); } } ...... /** * cancel timer and timer task */ private void cancelUltrasoundTimer(){ if (mTimer != null) { mTimer.cancel(); mTimer = null; } if (mTimerTask != null){ mTimerTask.cancel(); mTimerTask = null; } } /** * restart timer to update UI */ private void restartUltrasoundTimer(String timer){ cancelUltrasoundTimer(); mTimer = new Timer(timer); mTimerTask = new TimerTask() { @Override public void run() { mHandler.sendEmptyMessageAtTime(1300, 50); } }; mTimer.schedule(mTimerTask, 50, 50); } 使用 Runnable 和 Handler 来定时更新UI Handler 部分不变,在开启的子线程中向Handler发送消息 onCreate 方法中启动子线程 Thread t = new Thread(new UpdateUIThread()); t.start(); class UpdateUIThread implements Runnable { @Override public void run() { while (true) { while (ultraStatus) { Message message = new Message(); message.what = UPDATE_ULTRA_VALUE;// int mHandler.sendMessage(message); try { Thread.sleep(100); // 暂停100ms,起到定时的效果 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } }
Android handler 可能会造成内存泄露 Android Studio 使用 Handler 时; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); // handle something } }; Android Studio 弹出了警告; This Handler class should be static or leaks might occur (null) less... (Ctrl+F1) Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object. 如果这个Handler用的是主线程外的Looper或者消息队列就没事。若是在主线程中,Handler的消息还在队列中尚未被处理,GC有可能无法回收这个Handler;也就无法回收使用这个Handler的外部类(如Activity、Service),有可能引起OOM。 换句话说,当Activity结束时,延迟的消息还可能在消息队列中。消息引用这Activity的Handler,Handler持有着它的外部类(也就是这个Activity)。这个引用会持续到消息被处理,在此之前不会被GC。 Java中,非静态内部类和匿名类会引用了它们的外部类。 根据修改建议,按照提示进行修改,将handler声明为static。 static class rustHandler extends Handler { private final WeakReference<MySwipeActivity> mActivity; rustHandler(MySwipeActivity mySwipeActivity) { mActivity = new WeakReference<MySwipeActivity>(mySwipeActivity); } @Override public void handleMessage(Message msg) { MySwipeActivity activity = mActivity.get(); if (activity != null) { // la la la la la ... } } } 静态类 rustHandler 中定义一个 WeakReference(弱引用);利用这个若引用来完成操作。这样警告就消失了。 参考Android源码,也有这种做法,没有使用弱引用的 static class MyHandler extends Handler { MySwipeActivity handlerSwipeActivity; MyHandler(MySwipeActivity mySwipeActivity) { handlerSwipeActivity = mySwipeActivity; } @Override public void handleMessage(Message msg) { if (handlerSwipeActivity != null) { // do something } } } 同样消除了AS的警告;MyHandler 声明为 static,达到了目的 Java中的引用 java.lang.ref 类库包含了一组类,这些类为垃圾回收提供了更大的灵活性。 SoftReference WeakReference PhantomReference 由强到弱,对应不同级别的“可获得性”;越弱表示对垃圾回收器的限制越少,对象越容易被回收。 参考http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.htmlhttp://stackoverflow.com/questions/11407943/this-handler-class-should-be-static-or-leaks-might-occur-incominghandler
Reverse bits of a given 32 bits unsigned integer. For example, given input 43261596 (represented in binary as 00000010100101000001111010011100), return 964176192 (represented in binary as00111001011110000010100101000000). 把一个无符号int数字,按二进制位反转过来 通过移位操作,一位一位来处理。目的是反转,所以先处理最右位。最右位(末端)一个一个地往result的左边推 public class ReverseBits { public static void main(String args[]) { System.out.print(solution(43261596)); } // &:当两边操作数的位同时为1时,结果为1,否则为0。如1100&1010=1000 // 按位反转一个int private static int solution(int input) { int result = 0; for (int i = 0; i < 32; i++) { // 一共32位 if (((input >> i) & 1) == 1) { // 如果移位后最右位为1 int j = 31 - i; // 判断当前是第几位,并换算成反转后的位数 result |= 1 << j; // 得知是反转后第几位,存入结果result中 } } return result; } } 输出: 964176192
Invert a binary tree 翻转一棵二叉树 假设有如下一棵二叉树: 4 / \ 2 7 / \ / \ 1 3 6 9翻转后: 4 / \ 7 2 / \ / \ 9 6 3 1 这里采用递归的方法来处理。遍历结点,将每个结点的两个子结点交换位置即可。 从左子树开始,层层深入,由底向上处理结点的左右子结点;然后再处理右子树 全部代码如下: public class InvertBinaryTree { public static void main(String args[]) { TreeNode root = new TreeNode(0); // 构建简单的二叉树 TreeNode node1 = new TreeNode(1); TreeNode node2 = new TreeNode(2); TreeNode node3 = new TreeNode(3); TreeNode node4 = new TreeNode(4); TreeNode node5 = new TreeNode(5); TreeNode node6 = new TreeNode(6); TreeNode node7 = new TreeNode(7); root.left = node1; root.right = node2; node1.left = node3; node1.right = node4; node2.left = node5; node2.right = node6; node4.right = node7; preOrderTravels(root); // 前序遍历一次 System.out.println(); root = invertBinaryTree(root); preOrderTravels(root); // 翻转后再前序遍历一次 } // 前序遍历 public static void preOrderTravels(TreeNode node) { if (node == null) { return; } else { System.out.print(node.val + " "); preOrderTravels(node.left); preOrderTravels(node.right); } } // 翻转二叉树 private static TreeNode invertBinaryTree(TreeNode root) { if (root == null|| (root.left == null && root.right == null)) return root;// 为空,或没有子树,直接返回 TreeNode tmp = root.right; // 右子树存入tmp中 root.right = invertBinaryTree(root.left);// 先处理左子树,然后接到root的右链接 root.left = invertBinaryTree(tmp); // 处理tmp中原来的右子树,然后接到root的左链接 return root; } } 输出:0 1 3 4 7 2 5 6 0 2 6 5 1 4 7 3
Android Bitmap 相关操作 常见的几个操作:缩放,裁剪,旋转,偏移 很多操作需要 Matrix 来支持;Matrix 通过矩阵来处理位图,计算出各个像素点的位置,从而把bitmap显示出来。 matrix里有一个3x3的矩阵,用于图像处理: MSCALE_X MSKEW_X MTRANS_X MSKEW_Y MSCALE_Y MTRANS_Y MPERSP_0 MPERSP_1 MPERSP_2 根据变量名能猜出具体的用途:缩放X 偏移X 平移X偏移Y 缩放Y 平移Y透视0 透视1 透视2 matrix的操作有set,pre和post;set能够直接设置矩阵中的数值;pre类似于矩阵左乘;post类似与矩阵中的右乘 原bitmap经过计算后,会重新生成一张bitmap 代码片段: /** * 根据给定的宽和高进行拉伸 * * @param origin 原图 * @param newWidth 新图的宽 * @param newHeight 新图的高 * @return new Bitmap */ private Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) { if (origin == null) { return null; } int height = origin.getHeight(); int width = origin.getWidth(); float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight);// 使用后乘 Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); if (!origin.isRecycled()) { origin.recycle(); } return newBM; } /** * 按比例缩放图片 * * @param origin 原图 * @param ratio 比例 * @return 新的bitmap */ private Bitmap scaleBitmap(Bitmap origin, float ratio) { if (origin == null) { return null; } int width = origin.getWidth(); int height = origin.getHeight(); Matrix matrix = new Matrix(); matrix.preScale(ratio, ratio); Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); if (newBM.equals(origin)) { return newBM; } origin.recycle(); return newBM; } /** * 裁剪 * * @param bitmap 原图 * @return 裁剪后的图像 */ private Bitmap cropBitmap(Bitmap bitmap) { int w = bitmap.getWidth(); // 得到图片的宽,高 int h = bitmap.getHeight(); int cropWidth = w >= h ? h : w;// 裁切后所取的正方形区域边长 cropWidth /= 2; int cropHeight = (int) (cropWidth / 1.2); return Bitmap.createBitmap(bitmap, w / 3, 0, cropWidth, cropHeight, null, false); } /** * 选择变换 * * @param origin 原图 * @param alpha 旋转角度,可正可负 * @return 旋转后的图片 */ private Bitmap rotateBitmap(Bitmap origin, float alpha) { if (origin == null) { return null; } int width = origin.getWidth(); int height = origin.getHeight(); Matrix matrix = new Matrix(); matrix.setRotate(alpha); // 围绕原地进行旋转 Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); if (newBM.equals(origin)) { return newBM; } origin.recycle(); return newBM; } /** * 偏移效果 * @param origin 原图 * @return 偏移后的bitmap */ private Bitmap skewBitmap(Bitmap origin) { if (origin == null) { return null; } int width = origin.getWidth(); int height = origin.getHeight(); Matrix matrix = new Matrix(); matrix.postSkew(-0.6f, -0.3f); Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); if (newBM.equals(origin)) { return newBM; } origin.recycle(); return newBM; } 按钮的操作定义: @Override public void onClick(View v) { Bitmap originBM = BitmapFactory.decodeResource(getResources(), R.drawable.littleboygreen_x128); switch (v.getId()) { case R.id.btn1: {// 按尺寸缩放 effectTextView.setText(R.string.scale); Bitmap nBM = scaleBitmap(originBM, 100, 72); effectView.setImageBitmap(nBM); break; } case R.id.btn2: {// 按比例缩放,每次点击缩放比例都会不同 effectTextView.setText(R.string.scale_ratio); if (ratio < 3) { ratio += 0.05f; } else { ratio = 0.1f; } Bitmap nBM = scaleBitmap(originBM, ratio); effectView.setImageBitmap(nBM); break; } case R.id.btn3: {// 裁剪 effectTextView.setText("剪个头"); Bitmap cropBitmap = cropBitmap(originBM); effectView.setImageBitmap(cropBitmap); break; } case R.id.btn4: {// 顺时针旋转效果;每次点击更新旋转角度 if (alpha < 345) { alpha += 15; } else { alpha = 0; } effectTextView.setText("旋转"); Bitmap rotateBitmap = rotateBitmap(originBM, alpha); effectView.setImageBitmap(rotateBitmap); break; } case R.id.btn5: {// 逆时针旋转效果;每次点击更新旋转角度 if (beta > 15) { beta -= 15; } else { beta = 360; } effectTextView.setText("旋转"); Bitmap rotateBitmap = rotateBitmap(originBM, beta); effectView.setImageBitmap(rotateBitmap); break; } case R.id.btn6: {// 偏移效果;偏移量在方法中 Bitmap skewBM = skewBitmap(originBM); effectView.setImageBitmap(skewBM); break; } } } 遇到的问题 Matrix matrix = new Matrix(); matrix.preScale(ratio, ratio);// 当 ratio=1,下面的 newBM 将会等价于 origin Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false); if (!origin.isRecycled()) { origin.recycle(); } log如下,当ratio=1时,新bitmap和旧的bitmap同一地址 11-27 05:27:16.086 16723-16723/? D/rust: originBitmap = android.graphics.Bitmap@1e8849e11-27 05:27:16.086 16723-16723/? D/rust: newBitmap = android.graphics.Bitmap@1e8849e
排序 选择排序 selection 插入排序 insertion 希尔排序 shell 归并排序 快速排序 准备工作 交换方法,供后续调用: private static void exch(int a[], int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } 比较方法: /** * v < w 返回 true */ private static boolean less(Comparable v, Comparable w) { return v.compareTo(w) < 0;//v < w 返回 -1 } 选择排序 selection 首先,找到数组中最小的那个元素,其次,将其与数组第一个元素交换位置(如果第一个元素就是最小的,那么它自己和自己交换位置)。 接下来在剩下的元素中寻找最小的元素,与数组第二个元素交换位置,如此反复。 对于长度为N的数组,选择排序需要大约N^2/2次比较和N次交换 代码段: public static void select_sort(int a[]) { int N = a.length; for (int i = 0; i < N; i++) { int min = i;//假设最小位为最左位 for (int j = i + 1; j < N; j++) {//遍历后面的元素,找到最小值 if (less(a[j], a[min])) min = j;//比较后,min为最小元素索引 } exch(a, i, min);//交换位置 } } 运行时间与输入无关;数据移动是最少的,只有最小值才会移动。 插入排序 insertion 游标从索引为1的位置往右走;对比游标以及游标左边的元素,向左边交换元素; 从游标到起始点,向左边一位一位地比较并交换元素; 小的元素会往左边一位一位地移动;当索引到达右端,排序完成。 对于随机排列的长度为N且主键不重复的数组,平均情况下插入排序需要约N^2/4次比较, 以及约N^2/4次交换。最坏情况下要约N^2/2次比较和N^2/2次交换。 最好情况下N-1次比较,0次交换 插入排序对常见的某些类型的非随机数组很有效。 代码段: public static void insertion_sort(int a[]) { int N = a.length; for (int i = 1; i < N; i++) { for (int j = i; j > 0 && (a[j] < a[j - 1]); j--) { exch(a, j, j - 1); } } } 部分有序 倒置指的是数组中两个顺序颠倒的元素。比如EXAMPLE中有11对倒置。E-A等等。 如果数组中倒置的数量小于数组大小的某个倍数,那么我们说这个数组是部分有序的。 几种典型部分有序数组: 数组中每个元素距离它的最终位置不远 一个有序的大数组接一个小数组 数组中只有几个元素位置不正确 插入排序对部分有序的数组很有效,选择排序则不然 希尔排序 shell 基于插入排序的快速的排序算法 对于大规模乱序数组,插入排序很慢,因为它只会交换相邻的元素,因此元素只能从一端缓慢移动到另一端。 希尔排序的思想是使数组中任意间隔为h的元素都是有序的。这样的数组称为h有序数组。 实现希尔排序的一种方法是对每个h,用插入排序将h个子数组独立地排序。 在插入排序中加入一个外循环while (h >= 1),插入排序以h为间隔 得到一个简洁的希尔排序 代码段: public static void shell_sort(int a[]) { int N = a.length; int h = 1; while (h < N / 3) { h = 3 * h + 1;// 找到最大的h } while (h >= 1) { for (int i = h; i < N; i++) { for (int j = i; j >= h && less(a[j], a[j - h]); j -= h) { exch(a, j, j - h);// 插入排序 } } h = h / 3;// 当h=1时,变成了插入排序 } } 希尔排序比插入和选择排序都快得多,并且数组越大优势越大。 理念: 为何要研究算法的设计和性能? 原因之一:提升速度来解决其他方式无法解决的问题 归并排序 将两个有序的数组归并成一个更大的有序数组 递归实现的归并排序是算法设计中分治思想的典型应用。 我们将一个大问题分割成小问题分别解决,然后用小问题的答案来解决整个大问题。 归并排序,将任一长度为N的数组排序所需的时间和NlogN成正比 原地归并的抽象方法 原地归并的抽象方法,需要一个辅助数组 /************************************** * 原地归并方法 **************************************/ private static void merge(int a[], int low, int mid, int high) { int i = low;// 左数组索引 int j = mid + 1;// 右数组索引 int temp[] = new int[high + 1]; for (int k = low; k <= high; k++) { temp[k] = a[k];// 全部复制到辅助数组中 } for (int k = low; k <= high; k++) { if (i > mid) { a[k] = temp[j++];// 若左数组用尽(左游标走到了右数组)直接取右数组的元素 } else if (j > high) { a[k] = temp[i++];// 若右数组用尽,取左数组的值 } else if (temp[i] < temp[j]) { a[k] = temp[i++];// 哪个小取哪个的 } else { a[k] = temp[j++];// i j 不要写错了 } } } 自顶向下的归并排序 如果它能将两个子数组排序,它就能够通过归并两个子数组来将整个数组排序 分治思想最典型的一个例子 以int[]为例;基于原地归并的抽象实现了另一种归并 private static void mergeSort(int a[]) { iMergeSort(a, 0, a.length - 1); } private static void iMergeSort(int a[], int low, int high) { if (high <= low) return; int mid = low + (high - low) / 2; iMergeSort(a, low, mid); // 将左半部分排序 iMergeSort(a, mid + 1, high); // 将右半部分排序 merge(a, low, mid, high); // 调用原地归并方法 } 命题:对于长度为N的任意数组,自顶向下的归并排序需要1/2NlgN至NlgN此比较 自底向上的归并排序 先归并微型数组,然后再成对归并得到的子数组。直到将整个数组归并在一起。 步骤 1.第一层循环,分割成小数组。小数组长度每次都翻倍。 2.第二层循环,两两归并小数组。 会多次遍历数组,根据子数组大小进行两两归并。子数组的大小sz的初始值为1,每次加倍。 /***************************************************** * 自底向上的归并排序 *****************************************************/ public static void mergeSortBU(int a[]) { int N = a.length; // sz 是子数组大小,会翻倍增加 for (int sz = 1; sz < N; sz = sz + sz) {// low 是子数组的索引 for (int low = 0; low < N - sz; low += sz + sz) { merge(a, low, low + sz - 1, Math.min(low + sz + sz - 1, N - 1)); } } } 当数组长度为2的幂时,自顶向下和自底向上的归并排序所用的比较次数和数组访问次数相同,只是顺序不同。 自底向上的归并排序比较适合链表组织的数据。 快速排序 原地排序,且将长度为N的数组排序所需时间与NlgN成正比 缺点是非常脆弱 基本算法 分治的排序算法。将一个数组分成两个子数组,两部分分别独立地进行排序。 快速排序和归并排序是互补的。 一个数字被分为两部分,当两个子数组都有序时,整个数组就有序了。 方法的关键在于切分,这个过程使得数组满足以下三个条件: 对于某个j,a[j]已经排定 a[lo]到a[j-1]中的所有元素都不大于a[j] a[j+1]到a[hi]中的所有元素都不小于a[j] 通过递归地调用切分来排序。因为切分总是能排定一个元素。 /***************************************************** * 快速排序方法 *****************************************************/ public static void quickSort(int a[]) { quickSort(a, 0, a.length - 1); } private static void quickSort(int a[], int lo, int hi) { if (hi <= lo) return; int j = partition(a, lo, hi); // 通过递归地调用切分来排序 quickSort(a, lo, j - 1); // 递归后优先处理左边的元素 quickSort(a, j + 1, hi); } /** * 切分方法 */ private static int partition(int a[], int lo, int hi) { int i = lo; // 扫描左指针 int j = hi + 1; // 右指针 int v = a[lo]; // 取a[lo]为切分元素 while (true) { // 扫描 while (less(a[++i], v)) if (i == hi) break; while (less(v, a[--j])) if (j == lo) break; if (i >= j) break; // 扫描结束条件:i与j相遇主循环退出 exch(a, i, j); // 扫描找到左右符合条件的元素,交换位置 } exch(a, lo, j); // 将切分元素放到正确的位置 return j; // 返回切分元素的索引 } 总是把小的移到a[lo]那边去 性能特点 快速排序的速度优势在于它的比较次数很少。 快速排序最好的情况是每次都正好能将数组对半分。 命题:将长度为N的无重复数组排序,快速排序平均需要~2NlgN次比较 命题:快速排序最多需要约N^2/2次比较,但随机打乱数组能够预防这种情况。 移动数据的次数少,就会更快
Android 自定义 permission Android 添加自定义权限 permission-tree 权限的根节点,3个成员都要定义 name 一般来说需要2个“.”;比如下面的"rust.permission.user"; 否则报错INSTALL_PARSE_FAILED_MANIFEST_MALFORMED icon 和 label 正常添加即可 permission 权限声明,定义权限组、等级等信息 uses-permission 使用权限 <!-- user define permission permission tree and permission --> <permission-tree android:name="rust.permission.user" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" /> <permission android:name="rust.permission.user.TEST" android:label="@string/app_name" android:permissionGroup="@string/action_settings" android:protectionLevel="normal" /> <!-- use user permission --> <uses-permission android:name="rust.permission.user.TEST" /> 代码中检查是否申请了权限 使用PackageManager的方法来检查 private static final String TestPermission = "rust.permission.user.TEST"; ...... checkUserPermission(getApplicationContext(), TestPermission); ...... /** * check permission * * @param context - the application context */ private void checkUserPermission(Context context, String permissionName) { PackageManager pm = getPackageManager(); boolean permitTest = (PackageManager.PERMISSION_GRANTED == pm.checkPermission(permissionName, getPackageName())); Toast.makeText(context, permitTest ? "Test YES!" : "Test NO!", Toast.LENGTH_SHORT) .show(); }
Android 自定义帧动画 Android L ; Android Studio 帧动画 和gif图片类似,顺序播放准本好的图片文件;图片资源在xml文件中配置好 将图片按照预定的顺序一张张切换,即成动画 Android 帧动画例子 可以把动画放进子线程中启动,也可以在主线程直接启动动画 主线程更容易控制动画的启停; 子线程需要关注线程的状态,不好控制动画 主线程的UI不能放进子线程去设置;即子线程不能直接修改主UI; 屏幕旋转后,activity重启;动画也就停止了; 在 AndroidManifest.xml 设置 configChanges 即可 <activity android:name=".MainActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> 动画资源 图片资源来自Android L Launcher3 res 图片全部放在 res/drawable 里面 配置文件 transition_stack.xmloneshot="false" 动画会一直循环播放下去 <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/transition_stack" android:oneshot="false"> <item android:drawable="@drawable/stack_00000" android:duration="30" /> <item android:drawable="@drawable/stack_00001" android:duration="30" /> ...... </animation-list> Java代码 1.取得ImageView 2.为ImageView设置背景资源文件 3.把ImageView的背景赋给动画AnimationDrawable public class MainActivity extends AppCompatActivity { private ImageView mTransitionIcon; private ImageView mStackIcon; private AnimationDrawable frameAnimation; private AnimationDrawable stackAnimation; private Thread stackThread; private Button stopButton; public boolean action = false; private TextView tvState; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); tvState = (TextView) findViewById(R.id.tv_state); stopButton = (Button) findViewById(R.id.btn_stop); Button btn1 = (Button) findViewById(R.id.btn1); Button btn2 = (Button) findViewById(R.id.btn2); /************************************************* * AnimationDrawable extends DrawableContainer *************************************************/ // 1.取得ImageView mTransitionIcon = (ImageView) findViewById(R.id.settings_transition_image); // 2.为ImageView设置背景资源文件 mTransitionIcon.setBackgroundResource(R.drawable.transition_none); // 3.把ImageView的背景赋给动画AnimationDrawable frameAnimation = (AnimationDrawable) mTransitionIcon.getBackground(); mStackIcon = (ImageView) findViewById(R.id.transition_stack); mStackIcon.setBackgroundResource(R.drawable.transition_stack); stackAnimation = (AnimationDrawable) mStackIcon.getBackground(); stackThread = new Thread() { @Override public void run() { stackAnimation.start();// 子线程中开始动画 } }; btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { action = !action;// 主线程中控制动画启动与停止 if (action) { frameAnimation.start(); // 启动(重启)动画 } else { frameAnimation.stop(); // 停止动画 } } }); btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (stackThread.getState() == (Thread.State.NEW)) stackThread.start();// 放到子线程中开启动画 }// 先查询子线程状态再启动,避免Thread报错导致app退出 }); stopButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String s = "";// 用于显示状态 s = action ? "action! " + stackThread.getState().toString() : "stop!" + stackThread.getState().toString(); tvState.setText(s); } }); } } Thread 类 状态一览: /** * A representation of a thread's state. A given thread may only be in one * state at a time. */ public enum State { /** * The thread has been created, but has never been started. */ NEW, /** * The thread may be run. */ RUNNABLE, /** * The thread is blocked and waiting for a lock. */ BLOCKED, /** * The thread is waiting. */ WAITING, /** * The thread is waiting for a specified amount of time. */ TIMED_WAITING, /** * The thread has been terminated. */ TERMINATED } 一个Button用于启动子线程,可以先判断子线程的状态,再决定是否启动
Android - Notification 使用 Android L ; Android Studio 14 使用过程 NotificationManager - 用于提示的管理,例如发送、取消 NotificationCompat.Builder - Builder模式构造notification;可参考《Effective Java》第2条 Notification - 提示,能够显示在状态栏和下拉栏上;构造实例能设定flags NotificationDemo 本例意在记录android notification的使用方法。 界面中放置了很多个按钮,每个按钮发送的提示并不完全相同。流程都一样。 设定一个NotificationManager, 使用NotificationCompat.Builder来建立Notification;点击按钮时NotificationManager.notify发送提示 其中有接收广播发送notification的例子 build.gradle部分代码,最低API 19: android { compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { applicationId "com.rust.rustnotifydemo" minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } 主要代码 MainActivity.java : package com.rust.rustnotifydemo; import android.app.NotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v4.app.NotificationCompat; import android.support.v7.widget.Toolbar; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; class notifyBroadcast extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { NotificationManager nMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); Intent goHome = new Intent(Intent.ACTION_MAIN); goHome.addCategory(Intent.CATEGORY_HOME); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.signal_horn_26px) .setContentText("点击返回桌面") .setContentTitle("Go home") .setTicker("来自广播的提示") .setContentIntent(PendingIntent.getActivity(context, 0, goHome, 0)); Notification notificationBroadcast = builder.build(); notificationBroadcast.flags |= Notification.FLAG_AUTO_CANCEL; nMgr.notify(002, notificationBroadcast);/* id相同;此提示与 Notification 2 只能显示一个 */ } } public class MainActivity extends AppCompatActivity { public static final String BroadcastNotify = "com.rust.notify.broadcast"; private EditText editContent; private Button sendNotification; private Button notifyButton1; private Button notifyButton2; private Button cleanButton; private Button notifyBroadcast; private int notificationId = 001; private BroadcastReceiver notifyReceiver; private InputMethodManager inputMgr; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); IntentFilter filter = new IntentFilter(BroadcastNotify); notifyReceiver = new notifyBroadcast(); registerReceiver(notifyReceiver, filter); /* get the widgets */ editContent = (EditText) findViewById(R.id.et_content); sendNotification = (Button) findViewById(R.id.btn_send_content); notifyButton1 = (Button) findViewById(R.id.btn_notify_1); notifyButton2 = (Button) findViewById(R.id.btn_notify_2); notifyBroadcast = (Button) findViewById(R.id.btn_notify_broadcast); cleanButton = (Button) findViewById(R.id.btn_clean_notification); /* 构造一个Bitmap,显示在下拉栏中 */ final Bitmap notifyBitmapTrain = BitmapFactory .decodeResource(this.getResources(), R.drawable.train); /* 管理器-用来发送notification */ final NotificationManager nMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notifyButton1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(getApplicationContext(), MainActivity.class); NotificationCompat.Builder nBuilder1 = new NotificationCompat.Builder(getApplicationContext()) .setTicker("Notify 1 ! ")/* 状态栏显示的提示语 */ .setContentText("Go back to RustNotifyDemo")/* 下拉栏中的内容 */ .setSmallIcon(R.drawable.notification_small_icon_24)/* 状态栏图片 */ .setLargeIcon(notifyBitmapTrain)/* 下拉栏内容显示的图片 */ .setContentTitle("notifyButton1 title")/* 下拉栏显示的标题 */ .setContentIntent(PendingIntent .getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); /* 直接使用PendingIntent.getActivity();不需要实例 */ /* getActivity() 是 static 方法*/ Notification n = nBuilder1.build();/* 直接创建Notification */ n.flags |= Notification.FLAG_AUTO_CANCEL;/* 点击后触发时间,提示自动消失 */ nMgr.notify(notificationId, n); } }); notifyButton2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { NotificationCompat.Builder nBuilder2 = new NotificationCompat.Builder(getApplicationContext()) .setTicker("Notify 2 ! ")/* 状态栏显示的提示语 */ .setContentText("Notification 2 content")/* 下拉栏中的内容 */ .setSmallIcon(R.drawable.floppy_16px)/* 状态栏图片 */ .setLargeIcon(notifyBitmapTrain)/* 下拉栏内容显示的图片 */ .setContentTitle("title2");/* 下拉栏显示的标题 */ nMgr.notify(notificationId + 1, nBuilder2.build()); /* 两个id一样的notification不能同时显示,会被新的提示替换掉 */ } }); sendNotification.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String content = editContent.getText().toString(); if (content.equals("")) { content = "U input nothing"; } NotificationCompat.Builder contentBuilder = new NotificationCompat.Builder(getApplicationContext()) .setTicker(content)/* 状态栏显示的提示语 */ .setContentText("I can auto cancel")/* 下拉栏中的内容 */ .setSmallIcon(R.drawable.rain_32px)/* 状态栏图片 */ .setLargeIcon(notifyBitmapTrain)/* 下拉栏内容显示的图片 */ .setContentTitle("Edit title");/* 下拉栏显示的标题 */ Notification n = contentBuilder.build(); nMgr.notify(notificationId + 2, n); } }); notifyBroadcast.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(BroadcastNotify); sendBroadcast(i); } }); cleanButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nMgr.cancel(notificationId);/* 根据id,撤销notification */ } }); } /** * 点击空白处,软键盘自动消失 */ @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (getCurrentFocus() != null && getCurrentFocus().getWindowToken() != null) { inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMgr.hideSoftInputFromWindow( getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } } return super.onTouchEvent(event); } @Override protected void onDestroy() { unregisterReceiver(notifyReceiver); super.onDestroy(); } } MainActivity launchMode="singleInstance";便于返回 activity 图片资源都是网络下载
Android - 使用Volley请求网络数据 Android L ; Android Studio 14 个人使用volley的小记,简述使用方法,不涉及volley源码 准备工作 导入Volley.jar包:我使用的是现成的jar包,将其放到app/libs目录下即可 网上可以下载到Volley.jar包 使用volley源代码 从github上pull一个下来git pull https://github.com/mcxiaoke/android-volley.git 把这个文件夹放到工程中,与app目录同级 ImportTest/ ├── app ├── build.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ImportTest.iml ├── local.properties ├── settings.gradle └── volley 在android studio中,编辑ImportTest/settings.gradle,加入':volley' include ':app',':volley' 编辑ImportTest/app/build.gradle;在dependencies中加入compile project(':volley') dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':volley')//加进来 testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.0.1' compile 'com.android.support:design:23.0.1' } 同步一下gradle;可能会错:Gradle sync failed: SSL peer shut down incorrectly 找到这个地方volley/build.gradle,注释掉这两句 //apply from: 'https://raw.github.com/mcxiaoke/gradle-mvn-push/master/jar.gradle' //apply from: 'https://raw.github.com/mcxiaoke/gradle-mvn-push/master/gradle-mvn-push.gradle' 使用volley 导入jar包已经完成,接下来需要: 申请网络权限 <uses-permission android:name="android.permission.INTERNET"/> 建立网络请求队列 RequestQueue 准备Url 请求数据 主要代码在VolleyTest.java中,新建了一个LinearLayout来显示数据 加载String,图片,JSON数据;能够实现异步加载网络图片 VolleyTest.java /* 用于显示的控件 */ cityName = (TextView) findViewById(R.id.city_name); temper = (TextView) findViewById(R.id.temper); weatherType = (TextView) findViewById(R.id.weather_type); webText = (TextView) findViewById(R.id.web_text); cat = (ImageView) findViewById(R.id.image_cat); wallpaper = (NetworkImageView) findViewById(R.id.image_wallpaper); /* 0.准备url,放到HashMap中备用 */ Map<String, String> sourceUrl = new HashMap<>();/* store url */ sourceUrl.put("beijing", "http://www.weather.com.cn/adat/cityinfo/101010100.html"); sourceUrl.put("cat_earphone", "http://pic.cnblogs.com/avatar/706293/20150628195334.png"); sourceUrl.put("wallpaper0010", "http://s.cn.bing.net/az/hprichbg/" + "rb/MaroonBellsVideo_ZH-CN9667920788_1920x1080.jpg"); /* 1.建立RequestQueue */ RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext());/* context */ /* 2.请求JSON文件;这里利用的是天气预报接口 */ JsonObjectRequest jsonRequest = new JsonObjectRequest(sourceUrl.get("beijing"), null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject jsonObject) { Log.d("rust", jsonObject.toString()); /* 2.1处理JSON文件 */ try { JSONObject weather = jsonObject.getJSONObject("weatherinfo"); cityName.setText(weather.getString("city")); StringBuilder temperRange = new StringBuilder(); temperRange.append(weather.getString("temp1")); temperRange.append(" ~ "); temperRange.append(weather.getString("temp2")); temper.setText(temperRange.toString()); weatherType.setText(weather.getString("weather")); } catch (JSONException e) { e.printStackTrace(); cityName.setText("ERROR"); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.e("rust", volleyError.toString()); } }); /* 不要忘记添加到队列中 */ requestQueue.add(jsonRequest);/* add to request queue */ /* 3.请求网络图片 */ ImageRequest catRequest = new ImageRequest(sourceUrl.get("cat_earphone"), new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { cat.setImageBitmap(bitmap); } }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.e("rust", volleyError.toString()); } } ); requestQueue.add(catRequest);/* add to request queue */ /* 3.1异步加载图片 */ ImageLoader imageLoader = new ImageLoader(requestQueue, new BitmapCache()); ImageLoader.ImageListener listener = ImageLoader.getImageListener( wallpaper, R.drawable.orange01, R.drawable.orange02 );/* ImageView,默认显示图片,加载失败后显示的图片*/ imageLoader.get(sourceUrl.get("wallpaper0010"), listener, 400, 400);/* 可指定图片最大尺寸 */ wallpaper.setImageUrl(sourceUrl.get("wallpaper0010"), imageLoader); /* 显示图片 */ /* 4.获取文本,以获取网站文本为例 */ StringRequest stringRequest = new StringRequest( "http://www.cnblogs.com/", new Response.Listener<String>() { @Override public void onResponse(String response) { webText.setText(response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("TAG", error.getMessage(), error); } }); requestQueue.add(stringRequest); BitmapCache.java import android.graphics.Bitmap; import android.util.LruCache; import com.android.volley.toolbox.ImageLoader; public class BitmapCache implements ImageLoader.ImageCache { private LruCache<String, Bitmap> mCache; public BitmapCache() { int maxSize = 10 * 1024 * 1024;/* 10M */ mCache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight(); } }; } @Override public Bitmap getBitmap(String url) { return mCache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { mCache.put(url, bitmap); } } final Volley是一个不错的网络框架,源代码可以在frameworks/volley中找到 这里的代码仅仅是实现功能;具体使用中会发现,解析JSON时可能会出现乱码,受网络影响JSON可能加载很慢
突然想买一个kindle。 以前用过一段时间的kindle,感觉还行。电子墨水屏幕对眼睛比较好,眼睛不容易累。 最初的kindle反应慢,刷新慢。现在的kindle在参数上看起来好多了。 (图片来源网络:http://www.amazon.cn/ ) 电子阅读器的局限性在哪呢? 假设我在使用kindle,看到某一页时,突然想翻看前面的内容。如果翻页的速度太慢,非常影响用户体验。如果要前后文比较,还可能得跳来跳去。 kindle的屏幕就只有那么大。如何能像纸质书本一样,快速的翻看,对比不连续页的内容? 我的想法是增大屏幕。增大屏幕会影响便携性。采用折叠屏幕应该能解决这个问题。 试想一下,一个6英寸大的机器,打开后屏幕总面积能达到大约12英寸。像纸质书一样摊开。 翻页时可以选择,2个屏幕同时翻页;也可以固定某个屏幕不翻页,只翻另一个屏幕的内容。 也可以折叠起来使用,单屏模式就和原来一样。另一个屏幕还可以显示屏保。就像俄罗斯YotaPhone那样。 打开后,一边屏幕看书,另一边还可以做笔记。当然,左右屏幕能够完成同样的笔记功能,方便不同惯用手的人群。 既然能够折叠,阅读器整体不能做的太厚。希望厚度能和经典版的kindle差不多。 (两个kindle“拼”起来) 目前市面上有类似的产品。考虑到钱包的健康,暂时不买电子阅读器。
命令行启动DDMS工具,前提是有这个工具 ~/rustsoftware/adt-bundle-linux-x86_64-20140702/sdk/tools$ ./ddms 查看机器内存情况: adb shell进入机器 root@xxxx:/ # cat proc/meminfo MemTotal: 4194304 kB MemFree: 2148688 kB MemAvailable: 2760656 kB
给定一个int数组,找出所有的子集;结果要排好序 Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset must be in non-descending order. The solution set must not contain duplicate subsets. For example,If nums = [1,2,3], a solution is: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ] Java代码: 1 package com.rust.datastruct; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.List; 6 7 class SubsetsSolution { 8 public List<List<Integer>> subsets(int[] nums) { 9 List<List<Integer>> res = new ArrayList<List<Integer>>(); 10 List<Integer> temp = new ArrayList<Integer>(); 11 res.add(temp);/* the empty subset: [] */ 12 findSubsets(nums, 0, temp, res); 13 return res; 14 } 15 private void findSubsets(int nums[],int start, List<Integer> singleSet, 16 List<List<Integer>> result){ 17 if (start >= nums.length){ 18 return; 19 } 20 for (int i = start; i < nums.length; i++) { 21 /*keep singleSet*/ 22 List<Integer> subset = new ArrayList<Integer>(singleSet); 23 subset.add(nums[i]); 24 Collections.sort(subset); 25 result.add(subset);/*put in result*/ 26 findSubsets(nums, i + 1, subset, result); 27 } 28 } 29 } 30 public class Subsets { 31 private static SubsetsSolution solution; 32 private static List<List<Integer>> res; 33 public static void main(String args[]){ 34 int nums[] = {1,4,3,2}; 35 solution = new SubsetsSolution(); 36 res = solution.subsets(nums); 37 for (int i = 0; i < res.size(); i++) { 38 System.out.println(res.get(i)); 39 } 40 } 41 } 输出: [] [1] [1, 4] [1, 3, 4] [1, 2, 3, 4] [1, 2, 4] [1, 3] [1, 2, 3] [1, 2] [4] [3, 4] [2, 3, 4] [2, 4] [3] [2, 3] [2] 递归处理问题。从输出结果可以看出处理的流程。 找到第一个数,这里是“1”,然后“1”保持不动,找下一个数“4”。然后“1”和“4”不动,再接着找。 每个元素都有机会作为开头的数。从左往右遍历一次,每次都会找当前数的右边的可能组合。
Android DrawerLayout 的使用 Android L Android Studio 1.4 从主视图左侧能抽出一个导航栏,效果图: 点击后弹出新界面: 新界面也可以抽出左侧导航栏 1.配置xml文件,指定根视图和左右抽屉视图 2.Activity中加载xml文件,设定UI动作 代码 首先配置 main_layout.xml 文件;用v4包里的DrawerLayout,指定一个FrameLayout作为根视图,后续可以把Fragment插入到这个FrameLayout中 抽屉视图是一个RelativeLayout,里面承载着几个UI,ListView用于装按钮,下面是退出按键 1 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:id="@+id/drawer_layout" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent"> 5 <!-- 指定Framelayout为根视图 --> 6 <FrameLayout 7 android:id="@+id/root_framelayout" 8 android:layout_width="match_parent" 9 android:layout_height="match_parent"></FrameLayout> 10 11 <!-- The left navigation drawer --> 12 <RelativeLayout 13 android:id="@+id/left_relative_drawer" 14 android:layout_width="240dp" 15 android:layout_height="match_parent" 16 android:layout_gravity="start" 17 android:background="#ffffcc" 18 android:clickable="true"><!-- clickable="true" 防止点击事件穿透 --> 19 20 21 <ImageButton 22 android:id="@+id/imagebtn_left_head" 23 android:layout_width="120dp" 24 android:layout_height="120dp" 25 android:layout_centerHorizontal="true" 26 android:layout_marginTop="20dp" 27 android:background="@drawable/littleboygreen" 28 android:scaleType="fitXY" /> 29 30 <ListView 31 android:id="@+id/left_list" 32 android:layout_width="match_parent" 33 android:layout_height="wrap_content" 34 android:layout_below="@id/imagebtn_left_head" 35 android:choiceMode="singleChoice" 36 android:divider="@android:color/transparent" 37 android:dividerHeight="10dp" 38 android:paddingTop="5dp"></ListView> 39 40 <Button 41 android:id="@+id/btn_left_exit" 42 android:layout_width="140dp" 43 android:layout_height="wrap_content" 44 android:layout_below="@id/left_list" 45 android:layout_centerInParent="true" 46 android:layout_marginTop="50dp" 47 android:background="@drawable/button_corner_shape" 48 android:text="退出应用" 49 android:textSize="20sp" /> 50 51 <Button 52 android:id="@+id/btn_left_confirm" 53 android:layout_width="70dp" 54 android:layout_height="wrap_content" 55 android:layout_below="@id/left_list" 56 android:layout_marginLeft="30dp" 57 android:layout_marginTop="50dp" 58 android:background="@drawable/button_corner_shape" 59 android:text="退出" 60 android:textSize="20sp" /> 61 62 <Button 63 android:id="@+id/btn_left_cancel" 64 android:layout_width="70dp" 65 android:layout_height="wrap_content" 66 android:layout_below="@id/left_list" 67 android:layout_marginLeft="40dp" 68 android:layout_marginTop="50dp" 69 android:layout_toRightOf="@id/btn_left_confirm" 70 android:background="@drawable/button_cancel_corner_shape" 71 android:text="取消" 72 android:textSize="20sp" /> 73 74 </RelativeLayout> 75 76 77 </android.support.v4.widget.DrawerLayout> 在MainActivity.java的onCreate方法中加载这个layout setContentView(R.layout.main_layout); 主视图就只有一张背景,新写一个MainFragment加载进去 MainFragment.java public class MainFragment extends Fragment { ...... private Toolbar toolbar; ...... @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.main_relativelayout, container,false); /* 这里设置UI,设置按钮监听等等 */ /* 为防止单调,显示放屏幕分辨率和屏幕方向 */ return rootView; } /* 屏幕分辨率 */ private String getScreenRatio(){ StringBuilder ratio = new StringBuilder(""); mDisplay = ((WindowManager)getActivity().getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay();/* 先getActivity */ DisplayMetrics mDisplayMetrics = new DisplayMetrics(); mDisplay.getMetrics(mDisplayMetrics); mDisplay.getSize(mCurrentDisplaySize);/* 竖屏Point(1536, 1964) 横屏Point(2048, 1452) */ Log.d("rust","mCurrentDisplaySize "+ mCurrentDisplaySize); int screenWidth = mDisplayMetrics.widthPixels; int screenHeight = mDisplayMetrics.heightPixels; ratio.append(screenWidth); ratio.append(" x "); ratio.append(screenHeight); return ratio.toString(); } /* 获取屏幕方向 */ private void getOrientation() { config = getResources().getConfiguration();/* 检查屏幕方向 */ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE){ screenOrientation = "LANDSCAPE"; } else if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { screenOrientation = "PORTRAIT"; } else { screenOrientation = "Unknow"; } } } 回到MainActivity.java的onCreate方法,把MainFragment加载进去 mainFragment = new MainFragment();/* 指定一个单独的Fragment */ ...... final FragmentManager fragmentManager = getFragmentManager();/* 加载到root容器 */ fragmentManager.beginTransaction().add(R.id.root_framelayout, mainFragment).commit(); 同样在MainActivity.java的onCreate方法中加载一下导航栏 /* 左抽屉 */ leftDrawer = (RelativeLayout) findViewById(R.id.left_relative_drawer); /* 左列表在左抽屉里 */ leftList = (ListView) leftDrawer.findViewById(R.id.left_list); /* 适配器装载数据;即初始化导航列表;这里使用SimpleAdapter,加载自定义的LinearLayout作为按钮 */ contentLeftAdapter = new SimpleAdapter(this, leftDrawerListData(), R.layout.list_item_linearlayout, new String[]{"image","text"}, new int[]{R.id.image_left_item,R.id.tv_left_item}); leftList.setAdapter(contentLeftAdapter); /* 为list设置ClickListener;DrawerOnItemClickListener定义在下面*/ leftList.setOnItemClickListener(new DrawerOnItemClickListener()); imageBtnLeft.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { fragmentManager.beginTransaction() .replace(R.id.root_framelayout, mainFragment) .commit(); mDrawerLayout.closeDrawer(leftDrawer); } }); btnExit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { btnConfirmExit.setVisibility(View.VISIBLE); btnCancelExit.setVisibility(View.VISIBLE); btnExit.setVisibility(View.INVISIBLE); } }); btnCancelExit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { btnCancelExit.setVisibility(View.INVISIBLE); btnConfirmExit.setVisibility(View.INVISIBLE); btnExit.setVisibility(View.VISIBLE); } }); btnConfirmExit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // finish(); android.os.Process.killProcess(android.os.Process.myPid()); } }); 往listview中添加内容的方法 private List<Map<String, Object>> leftDrawerListData(){ List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put("image", R.drawable.orange01); map.put("text", "橘子"); list.add(map); map = new HashMap<String, Object>(); map.put("image", R.drawable.sixtraveltransportation); map.put("text", "路标"); list.add(map); map = new HashMap<String, Object>(); map.put("image", R.drawable.traintraveltransportation); map.put("text", "火车"); list.add(map); map = new HashMap<String, Object>(); map.put("image", R.drawable.ecologytree); map.put("text", "树苗"); list.add(map); map = new HashMap<String, Object>(); map.put("image", R.drawable.ecology); map.put("text", "插头"); list.add(map); return list; } 导航栏按钮监听 private class DrawerOnItemClickListener implements AdapterView.OnItemClickListener{ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { seleteItem(position);/* 按钮选择 */ } } private void seleteItem(int position){ leftList.setItemChecked(position, true); Fragment newFragment = new ContentFragment();/* new 一个子fragment */ Bundle args = new Bundle(); args.putInt(ContentFragment.ARG_SHOW_FRAGMENT,position); newFragment.setArguments(args);/* 装载数据 */ FragmentManager childFragmentManager = getFragmentManager(); childFragmentManager.beginTransaction() .replace(R.id.root_framelayout, newFragment) .commit();/* 替换当前fragment */ /* 最后关闭左侧抽屉 */ mDrawerLayout.closeDrawer(leftDrawer); } 点击一下导航栏的按钮,就会把原来的MainFragment替换掉 DrawerLayout往往和Fragment结合起来使用;布局界面可以更丰富和灵活 如果跳到另一个Activity,就抽不出MainActivity的DrawerLayout
输出二叉树的寻叶路径Given a binary tree, return all root-to-leaf paths. For example, given the following binary tree: 1 / \2 3 \ 5All root-to-leaf paths are: ["1->2->5", "1->3"] 遍历二叉树的时候,每当寻到叶结点,把路径装进结果里 1 package com.rust.datastruct; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * judging 8 * Binary Tree Paths 9 * 10 * Given a binary tree, return all root-to-leaf paths. 11 * 12 * For example, given the following binary tree: 13 * 14 * 1 15 * / \ 16 * 2 3 17 * \ 18 * 5 19 * All root-to-leaf paths are: 20 * ["1->2->5", "1->3"] 21 */ 22 class BinaryTreePathsSolution { 23 List<String> res = new ArrayList<String>(); 24 public List<String> binaryTreePaths(TreeNode root) { 25 if (root != null) track(root, root.val + "");/* String.valueOf(root.val)*/ 26 return res; 27 } 28 private void track(TreeNode n, String path){ 29 if (n.left == null && n.right == null) res.add(path); 30 if (n.left != null) track(n.left, path + "->" + n.left.val);/* continue tracking */ 31 if (n.right != null) track(n.right, path + "->" + n.right.val); 32 } 33 } 34 35 /** 36 * Test main 37 */ 38 public class BinaryTreePaths { 39 public static void main(String args[]) { 40 TreeNode root = new TreeNode(0); 41 TreeNode node1 = new TreeNode(1); 42 TreeNode node2 = new TreeNode(2); 43 TreeNode node3 = new TreeNode(3); 44 TreeNode node4 = new TreeNode(4); 45 TreeNode node5 = new TreeNode(5); 46 TreeNode node6 = new TreeNode(6); 47 TreeNode node7 = new TreeNode(7); 48 49 root.left = node1; 50 root.right = node2; 51 node1.left = node3; 52 node1.right = node4; 53 node2.left = node5; 54 node2.right = node6; 55 node4.right = node7; 56 57 BinaryTreePathsSolution solution = new BinaryTreePathsSolution(); 58 List<String> res = solution.binaryTreePaths(root); 59 60 for (int i = 0;i < res.size();i++){ 61 System.out.print(res.get(i) + " "); 62 } 63 System.exit(0); 64 } 65 66 } 输出: 0->1->3 0->1->4->7 0->2->5 0->2->6
二进制加法 输入2个字符串,字符串内由0和1组成;计算二者之和,返回字符串 Given two binary strings, return their sum (also a binary string). For example,a = "11"b = "1"Return "100". 1 package com.rust.TestString; 2 3 public class AddBinary { 4 public static String addBinary(String a, String b) { 5 int alen = a.length() - 1; 6 int blen = b.length() - 1; 7 int carry = 0; 8 String res = ""; 9 while (alen >=0 || blen >= 0 || carry == 1){ 10 int delta = (alen < 0)? 0 : a.charAt(alen--) - '0';/* 得到int */ 11 int beta = (blen < 0)? 0 : b.charAt(blen--) - '0'; 12 res = (char)('0' + delta ^ beta ^ carry) + res;/* 异或处理得到当前位 */ 13 carry = (delta + beta + carry) >> 1;/* 移位处理得到进位 */ 14 } 15 return res; 16 } 17 18 public static void main(String args[]){ 19 String text = "0101010"; 20 String atext = "111"; 21 String btext = "010"; 22 System.out.println(addBinary(atext, text)); 23 System.out.println(addBinary(atext, btext)); 24 System.out.println(addBinary(btext, text)); 25 } 26 } 输出: 011000110010101100
问题: 实现次方运算 Implement pow(x, n). 解法: Consider the binary representation of n. For example, if it is "10001011", then x^n = x^(1+2+8+128) = x^1 * x^2 * x^8 * x^128. Thus, we don't want to loop n times to calculate x^n. To speed up, we loop through each bit, if the i-th bit is 1, then we add x^(1 << i) to the result. Since (1 << i) is a power of 2, x^(1<<(i+1)) = square(x^(1<<i)). The loop executes for a maximum of log(n) times. n还大于0的时候,每次循环x都在平方。遇到位为1的时候,把x乘进去。 Java代码: public static double myPow(double x, int n) { if (n == 0) { return 1; } if (n < 0) { if (n == Integer.MIN_VALUE) { return 1.0 / (myPow(x, Integer.MAX_VALUE)*x); } else { return 1.0 / (myPow(x,-n)); } } double res = 1.0; for (;n > 0;x *= x,n>>=1) { if ((n & 1) > 0) { res *= x; } } return res; } public static double myPow(double x, int n) { if (n == 0) { return 1; } if (n < 0) { if (n == Integer.MIN_VALUE) { return 1.0 / (myPow(x, Integer.MAX_VALUE)*x); } else { return 1.0 / (myPow(x,-n)); } } double res = 1.0; while (n > 0) { if ((n & 1) > 0) { res *= x; } x *= x; n>>=1; } return res; }
实现取平方根的方法 输入int型,返回int型 使用二分法查找,慢慢逼近结果;注意防止溢出,直接用乘法的结果去比较 1 package com.rust.cal; 2 3 public class Sqrtx { 4 /** 5 * 二分法查找 6 * @param x-目标值 7 * @return x的int型平方根 8 */ 9 public static int mySqrt(int x) { 10 double diff = 0.000001f; 11 double start = 0; 12 double end = x; 13 double mid; 14 while(end - start > diff){ 15 mid = (end + start)/2; 16 if (mid * mid == x) { 17 return (int) mid; 18 } 19 if (mid*mid > x) { 20 end = mid; 21 } else{ 22 start = mid; 23 } 24 } 25 return (int) end; 26 } 27 public static void main(String args[]){ 28 System.out.println(mySqrt(2147483647)); 29 System.out.println(mySqrt(1)); 30 System.out.println(mySqrt(0)); 31 } 32 }
搜索2维数组;如果数组中存在目标值,返回true,否则返回false Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: Integers in each row are sorted in ascending from left to right. Integers in each column are sorted in ascending from top to bottom. For example, Consider the following matrix: [ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30] ] Given target = 5, return true. Given target = 20, return false. 遍历的话肯定效率低下。设立游标移动搜索。那么问题是从哪里开始搜索? 比较好的办法是从右上角开始。如果右上角的数字大于target,那么这一列没有target;向左一列移动 如果小于target,那么这一行没有target,向下一行移动 搜索停止的条件就是越界 Java代码: 1 package com.rust.cal; 2 3 public class Searcha2DMatrixII { 4 5 public static boolean searchMatrix(int[][] matrix, int target) { 6 if (matrix.length == 0 || matrix[0].length == 0) { 7 return false; 8 } 9 int dy = matrix[0].length - 1; 10 int delta = matrix[0][dy]; 11 int dx = 0; 12 while(dx < matrix.length && dy >= 0){ 13 if (matrix[dx][dy] == target) { 14 return true; 15 } else if (matrix[dx][dy] < target) { 16 dx++; 17 } else { 18 dy--; 19 } 20 } 21 return false; 22 } 23 public static void main(String args[]){ 24 int matrix[][] = { 25 {1 ,2 ,3 ,4 ,9 }, 26 {6 ,7 ,8 ,10,19}, 27 {17,18,30,31,32} 28 }; 29 30 int matrix1[][] = { 31 {1,2}, 32 {4,5}, 33 {6,17}, 34 {10,20} 35 }; 36 37 int matrix2[][] = { 38 {1,3,5,7,9,13,17}, 39 {2,4,6,8,10,16,20} 40 }; 41 42 System.out.println("matrix has 17? " + searchMatrix(matrix, 17)); 43 System.out.println("matrix has 18? " + searchMatrix(matrix, 18)); 44 System.out.println("matrix has 19? " + searchMatrix(matrix, 19)); 45 System.out.println("matrix1 has 4? " + searchMatrix(matrix1, 4)); 46 System.out.println("matrix2 has 4? " + searchMatrix(matrix2, 4)); 47 48 } 49 } 输出: matrix has 17? true matrix has 18? true matrix has 19? true matrix1 has 4? true matrix2 has 4? true
不能使用乘法,除法和mod operator,实现除法功能。 Divide two integers without using multiplication, division and mod operator. If it is overflow, return MAX_INT. 用减法可以实现,但是太慢了。算法里所做的优化都是为了节省时间。 不能忽视溢出的问题。例如,被除数是Integer.MIN_VALUE,除以-1,得到的结果对于Int型来说就溢出了,因此返回Integer.MAX_VALUE 先处理符号,处理两个极端情况。其余的先转为long正数后递归处理。 除数每次左移位数都增加,这加快了运算效率。把每次递归产生的左移次数都加起来,即是结果。 Java代码: public int divide(int dividend, int divisor) { int sign = ((dividend ^ divisor)>>>31) == 1? -1:1; if (dividend == 0) { return 0; } if (divisor == 0) { return Integer.MAX_VALUE; } if(divisor == -1 && dividend == Integer.MIN_VALUE) { return Integer.MAX_VALUE;//overflow } long n1 = Math.abs((long) dividend);//avoid overflow long n2 = Math.abs((long) divisor); return (int) (div(n1,n2)*sign); } public long div(long n1, long n2){ if (n1 < n2) { return 0; } int i = 0; while(n1 > (n2 << (i+1))){ i++; } return (1<<i) + div(n1 - (n2<<i), n2); } 这个问题有3点启示: 1、注意溢出的问题,该转换的就转换 2、关注运行效率的问题 3、移位方法的运用
实现strStr(),返回下标数字或-1 Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. 1 package com.rust.TestString; 2 3 public class ImplementstrStr { 4 public static int strStr(String haystack, String needle) { 5 int index = 0; 6 int res = 0; 7 int len = haystack.length(); 8 int nlen = needle.length(); 9 if (haystack.equals(needle) || nlen == 0) { 10 return 0; 11 } 12 if (len < nlen) { 13 return -1; 14 } 15 if (len == 1 && haystack.equals(needle)) { 16 return 0; 17 } 18 19 for (int i = 0; i < haystack.length(); i++) { 20 if (haystack.charAt(i) == needle.charAt(0)) { 21 if (len - i < nlen) { 22 return -1; 23 } 24 index = i + 1; 25 res = i; 26 int j = 1; 27 28 while (j < needle.length()){ 29 if (haystack.charAt(index) == needle.charAt(j)) { 30 index++; 31 j++; 32 } else { 33 break; 34 } 35 } 36 if (index - res == nlen) { 37 return res; 38 } 39 } 40 } 41 return -1; 42 } 43 44 public static void main(String args[]){ 45 System.out.println(strStr("abc", "c")); 46 System.out.println(strStr("aadbdffad", "df")); 47 System.out.println(strStr("dbdffad", "df")); 48 System.out.println(strStr("dbdffad", "")); 49 } 50 }
Java Swing intro 如果有Android app开发经验,快速上手Swing不是问题。UI方面有相似的地方。 简单的几行代码就能抛出一个框框,记录一下操作过程 1.先显示一个框框 EraseBlockGame类是主类,包含了main入口,继承自 JFrame public class EraseBlockGame extends JFrame{ ...... public EraseBlockGame(String GameTitle){ // 构造方法 super(GameTitle); setSize(408, 640); setLocationRelativeTo(null);// place in the center of screen ...... setVisible(true); } } 设置窗口大小,设置窗口在屏幕上的位置,窗口可见 public static void main(String args[]){ EraseBlockGame e = new EraseBlockGame("Erase Block Game"); } 运行一下程序,弹出一个窗口;窗口名称为Erase Block Game 2.菜单栏 菜单栏有菜单按钮,以及菜单选项 import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; JMenuBar 是整个菜单 JMenu 是菜单栏上的单个按钮 JMenuItem 点开单个餐单键,弹出的子选项item public class EraseBlockGame extends JFrame{ private static final long serialVersionUID = 1L; private JMenuBar menuBar = new JMenuBar(); private JMenu mGame = new JMenu("Game"); private JMenuItem miNewGame = new JMenuItem("New game"); private JMenuItem miExit = new JMenuItem("Exit"); ...... } 如果多几个选项,总是new似乎不大好,用简单工厂来代替new 定义JMenuFactory,里面有创建JMenu的方法 package com.rust.util; import javax.swing.JMenu; public class JMenuFactory { JMenu menu; public JMenuFactory(){ } public JMenu createMenu(String title){ JMenu menu = new JMenu(title); return menu; } } 同样定义JMenuItemFactory package com.rust.util; import javax.swing.JMenuItem; public class JMenuItemFactory { JMenuItem item; public JMenuItemFactory(){ } public JMenuItem createMenuItem(String title){ item = new JMenuItem(title); return item; } } 原来的new就可以替换为 private JMenu mGame; private JMenu mControl; private JMenu mInfo; private JMenuItem miNewGame; private JMenuItem miExit; ...... mGame = menuFactory.createMenu("Game"); mControl = menuFactory.createMenu("Control"); mInfo = menuFactory.createMenu("Info"); miNewGame = miFactory.createMenuItem("New game"); miExit = miFactory.createMenuItem("Exit"); 在构造函数中给菜单item添加ActionListener,和Android app的Button差不多 miNewGame.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { } }); miExit.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); mGame.add(miNewGame);//这里添加的顺序就是排列的顺序 mGame.add(miExit);//往menu中添加子项 menuBar.add(mGame); menuBar.add(mControl);//这里添加的顺序就是排列的顺序 setJMenuBar(menuBar); 如此看来,Swing活在了Android中 3.放置按钮 此时界面上只有一些菜单按键,多摆几个按钮上去看看 定义一个控制面板类ControlBoard 继承自 JPanel /** * 控制面板,提供很多快捷的控制功能 * @author Rust Fisher */ public class ControlBoard extends JPanel{ private JButton btnStart; private JButton btnStop; private JButton btnPause; private JButton btnReset; private JButton btnExit; /*定义一个按钮区域areaButton,用来存放btn*/ private JPanel areaButton = new JPanel(new GridLayout(5, 1)); private EraseBlockGame game; /*按钮区域的框框*/ private Border border = new EtchedBorder(EtchedBorder.RAISED, Color.WHITE,Color.gray); public ControlBoard(final EraseBlockGame game){ setLayout(new GridLayout(3,1,0,1)); this.game = game;//用于控制 btnStart = new JButton("Start"); btnStart.setEnabled(true); btnStop = new JButton("Stop"); btnStop.setEnabled(false); btnPause = new JButton("Pause"); btnPause.setEnabled(false); btnReset = new JButton("Reset"); btnReset.setEnabled(true); btnExit = new JButton("Exit"); btnExit.setEnabled(true); areaButton.add(btnStart); areaButton.add(btnPause); areaButton.add(btnStop); areaButton.add(btnReset); areaButton.add(btnExit); areaButton.setBorder(border); add(areaButton);// 把按钮区添加到控制面板上 btnStart.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // go go go } }); btnExit.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0);//886 } }); } ...... } 在EraseBlockGame类里加载按钮区域 public class EraseBlockGame extends JFrame{ ...... private ControlBoard controlBoard; public EraseBlockGame(String title){ ...... Container container = getContentPane(); controlBoard = new ControlBoard(this); container.add(controlBoard, BorderLayout.EAST);//添加控制面板 ...... } } 于是按钮就被装到程序上了 其他的就先不纠结了,Swing了解个大概就好;可以多看看android开发
Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2->3->4, you should return the list as 2->1->4->3. Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed. 给定一个链表,把相邻两个结点调换位置;返回head Java代码: 1 package com.rust.cal; 2 3 /** 4 * Definition for singly-linked list. 5 * public class ListNode { 6 * int val; 7 * ListNode next; 8 * ListNode(int x) { val = x; } 9 * } 10 */ 11 public class SwapNodesinPairs { 12 public static ListNode swapPairs(ListNode head) { 13 if (head == null || head.next == null) { 14 //必须先判断head是否为null,否则会出java.lang.NullPointerException 15 //如果输入的head == null,先判断head.next会找不到目标 16 return head; 17 } 18 /* 针对前两个结点 */ 19 ListNode pre = head.next, later, veryFirst; 20 head.next = pre.next; 21 pre.next = head; 22 head = pre; 23 later = head.next; 24 /* 25 * 针对后续结点 26 * 连续有2个结点,才进行换位 27 */ 28 while (later.next != null && later.next.next != null) { 29 veryFirst = later; 30 pre = pre.next.next; 31 later = later.next.next; 32 pre.next = later.next; 33 later.next = pre; 34 veryFirst.next = later; 35 later = pre; 36 pre = veryFirst.next; 37 } 38 return head; 39 } 40 41 public static void main(String args[]){ 42 /* 43 * prepare data 44 */ 45 ListNode head = new ListNode(1); 46 ListNode initHead = head; 47 for (int i = 2; i < 10; i++) { 48 initHead.next = new ListNode(i); 49 initHead = initHead.next; 50 } 51 52 head = swapPairs(head); 53 /* 54 * show data 55 */ 56 ListNode newHead = head; 57 while(newHead != null){ 58 System.out.print(newHead.val + " "); 59 newHead = newHead.next; 60 } 61 ListNode nothing = new ListNode(1); 62 swapPairs(nothing.next); 63 } 64 } 输出: 2 1 4 3 6 5 8 7 9 这个方法是先处理前2个结点,再循环处理后续的结点。其实结点的处理方法都差不多,在LeetCode讨论区看到递归解法,搬运过来 Java代码: public ListNode swapPairs(ListNode head) { if (head == null || head.next == null) return head; ListNode n1 = head; ListNode n2 = head.next; n1.next = n2.next; n2.next = n1; n1.next = swapPairs(n1.next); return n2; } 利用方法开头对head是否为null的判断作为递归的条件,比第一个方法优雅很多
ubuntu14.04 压缩图片default_wallpaper.jpg(2048x1536);压缩后ooo.jpg(1920x1280) if( 宽 > 高 ){ convert default_wallpaper.jpg -resize x1920 -gravity center -extent 1920x1280 ooo.jpg } else{ convert default_wallpaper.jpg -resize 1920x -gravity center -extent 1920x1280 ooo.jpg } 1920x1280表示分辨率 压缩图片,不加上-extend命令 convert littleboygreen.png -resize x72 -gravity center p1.png 如果有extend命令,图片空白的地方会被填充 可以安装GIMP这个软件,这是一款类似于ps的修图软件
Remove Element删掉指定的元素,并用后面的元素顶替空出来的位置;Remove ElementGiven an array and a value, remove all instances of that value in place and return the new length.The order of elements can be changed. It doesn't matter what you leave beyond the new length.遍历数组;若元素nums[i]不等于指定数值val,保存此元素,游标newLengh加1 java代码: 1 package com.rust.cal; 2 3 public class RemoveElement { 4 public static int removeElement(int[] nums, int val) { 5 if (nums.length == 0) { 6 return 0; 7 } 8 int newLengh = 0; 9 for (int i = 0; i < nums.length; i++) { 10 if (nums[i] != val) { 11 nums[newLengh] = nums[i]; 12 newLengh++; 13 } 14 } 15 return newLengh; 16 } 17 public static void main(String args[]){ 18 int[] input = {4,5,8,4,5,4,9}; 19 int val = 4; 20 System.out.print("input = {"); 21 for (int i = 0; i < input.length - 1; i++) { 22 System.out.print(input[i] + ", "); 23 } 24 System.out.print(input[input.length - 1] + "}"); 25 System.out.println("\n" + "val = 4"); 26 System.out.println("new lengh = " + removeElement(input, val)); 27 System.out.print("output : "); 28 for (int i = 0; i < input.length; i++) { 29 System.out.print(input[i] + "\t"); 30 } 31 } 32 } 控制台输出:input = {4, 5, 8, 4, 5, 4, 9}val = 4new lengh = 4output : 5 8 5 9 5 4 9
Remove Duplicates from Sorted Array Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length. Do not allocate extra space for another array, you must do this in place with constant memory. For example, Given input array nums = [1,1,2], Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn't matter what you leave beyond the new length 删除有序数组中重复的元素;返回新的长度;不允许新建额外的数组 解决思路 设立2个游标:游标index用来遍历数组,游标newLen用来插入元素 遍历数组,两两比较;如果不同,将index指向的元素复制到newLen指向位置;newLen与index各自右移 package com.rust.cal; public class RemoveDuplicatesfromSortedArray { public static int removeDuplicates(int[] nums) { if (nums.length <= 1) { return nums.length; } int index = 1; int newLen = 1; // return value while(index < nums.length){ if (nums[index] != nums[index - 1]) { nums[newLen] = nums[index]; // insert element newLen++; } index++; } return newLen; } public static void main(String args[]){ int[] input = {1,2,2,3,4,5,5,5,5,5,6,6,6,7,7,7,7,8}; System.out.println("new lengh: " + removeDuplicates(input)); for (int i = 0; i < input.length; i++) { System.out.print(input[i] + " "); } } } 输出: new lengh: 8 1 2 3 4 5 6 7 8 5 5 6 6 6 7 7 7 7 8
手机按键组合,回溯 Letter Combinations of a Phone Number Given a digit string, return all possible letter combinations that the number could represent. A mapping of digit to letters (just like on the telephone buttons) is given below. Input:Digit string "23" Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. 1 package com.rust.TestString; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class LetterCombinationsofaPhoneNumber { 7 public static List<String> letterCombinations(String digits) { 8 List<String> result = new ArrayList<String>(); 9 if (digits.equalsIgnoreCase("")) { 10 return result; 11 } 12 String[] keys = new String[10]; 13 keys[0] = ""; 14 keys[1] = ""; 15 keys[2] = "abc"; 16 keys[3] = "def"; 17 keys[4] = "ghi"; 18 keys[5] = "jkl"; 19 keys[6] = "mno"; 20 keys[7] = "pqrs"; 21 keys[8] = "tuv"; 22 keys[9] = "wxyz"; 23 char[] temp = new char[digits.length()]; 24 combine(keys, temp, digits, 0, result); 25 return result; 26 } 27 public static void combine(String[] keys, char[] temp, String digits, int index, List<String> res) { 28 if (index == digits.length()) { 29 res.add(new String(temp)); 30 return; // get out now 31 } 32 char digitChar = digits.charAt(index); 33 for (int i = 0; i < keys[digitChar - '0'].length(); i++) { // scan char at keys[digitChar - '0'] 34 temp[index] = keys[digitChar - '0'].charAt(i); 35 combine(keys, temp, digits, index + 1, res); 36 } 37 38 } 39 public static void main(String args[]){ 40 List<String> one = letterCombinations(""); 41 for (int i = 0; i < one.size(); i++) { 42 System.out.print(one.get(i)+" "); 43 } 44 } 45 }