<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
安卓提供了兩種方式的事件處理:基於回撥的事件處理和基於監聽的事件處理。
基於監聽的事件處理
基於監聽的事件處理一般包含三個要素,分別是:
Event Source(事件源):事件發生的場所,通常是各個元件
Event(事件):事件封裝了介面元件上發生的特定事件(通常就是使用者的一次操作)
Event Listener(事件監聽器):負責監聽事件源發生的事件,並對各種事件作出相應的響應
下面使用一個簡單的案例介紹按鈕事件監聽器
佈局檔案就是簡單的線性佈局器,上面是一個EditText,下面是一個Button按鈕
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal"> <EditText android:id="@+id/txt" android:layout_width="match_parent" android:layout_height="wrap_content" android:editable="false" android:cursorVisible="false" android:textSize="12pt"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/bn" android:text="單擊我"/> </LinearLayout>
使用Java程式碼給Button註冊一個事件監聽器
public class EventActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.event); Button bn = (Button) findViewById(R.id.bn); bn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditText txt = (EditText) findViewById(R.id.txt); txt.setText("bn按鈕被單擊了!"); } }); } }
單擊按鈕後,文字方塊就會顯示"bn按鈕被單擊了!"
外部類作為事件監聽器類
如果某個事件監聽器確實需要被多個GUI介面所共用,而且主要是完成某種業務邏輯的實現,那麼就可以考慮使用外部類形式來定義事件監聽器類。
我們定義一個類實現OnClickListener介面,並且實現onClick()方法
public class SendSmsListener implements View.OnClickListener { private Activity activity; private EditText address; private EditText content; public SendSmsListener(Activity activity, EditText adress, EditText content){ Toast.makeText(activity, "初始化完成", Toast.LENGTH_SHORT).show(); this.activity = activity; this.address = adress; this.content = content; } @Override public void onClick(View v) { String addressStr = address.getText().toString(); String contentStr = content.getText().toString(); // 獲取簡訊管理器 SmsManager smsManager = SmsManager.getDefault(); // 建立傳送簡訊想PendingIntent PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE); // 傳送簡訊文字 smsManager.sendTextMessage(addressStr, null, contentStr, pendingIntent, null); Toast.makeText(activity, "簡訊傳送完成", Toast.LENGTH_LONG).show(); } }
然後編輯一個簡單的線性佈局,有兩個輸入框和一個按鈕
<?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" android:gravity="center_horizontal"> <EditText android:id="@+id/edit1" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="50pt"/> <EditText android:id="@+id/edit2" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="50pt"/> <Button android:id="@+id/send" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="點選我傳送資訊" android:longClickable="true"/> </LinearLayout>
最後編寫一個Activity
public class SendMessageActivity extends Activity { private EditText editText1; private EditText editText2; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.message); editText1 = (EditText) findViewById(R.id.edit1); editText2 = (EditText) findViewById(R.id.edit2); Button button = (Button) findViewById(R.id.send); button.setTextSize(25); button.setOnClickListener(new SendSmsListener(this, editText1, editText2)); // 請求許可權 ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.SEND_SMS} , 1); } }
為了能夠順利傳送簡訊,需要重新開啟一臺模擬器,填寫模擬器的ID,否則程式會報錯
我的電腦設定沒有辦法同時執行兩個虛擬機器器,所以這裡就不展示了。
基於回撥的事件處理
回撥這個詞在程式設計領域經常被提及,我的理解是,回撥實際上是某個類中早已經定義好的方法或者介面,當我們繼承或者實現介面的時候,可以相應地重寫對應方法,或者實現相應介面。在程式執行的特定位置會呼叫特定的方法,當我們重寫了某個方法之後,就可以在特定情況下實現對應的邏輯。
最最簡單的一個例子就是Activity的onCreate()方法,當我們初始化一個Activity類的時候,就會呼叫這個方法,如果我們不重寫這個方法,那麼程式就會呼叫預設的onCreate()方法,如果我們重寫了這個方法,那麼程式就會呼叫我們重寫的onCreate()方法。
我們可以用回撥的方式實現一個跟隨手指的小球。
首先自定義一個自定義的View
public class DrawViewPlus extends View { public float currentX = 50; public float currentY = 50; // 定義建立畫筆 Paint p = new Paint(); public DrawViewPlus(Context context, AttributeSet set) { super(context, set); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 設定畫筆顏色 p.setColor(Color.RED); // 繪製小球 canvas.drawCircle(currentX, currentY, 15, p); } @Override public boolean onTouchEvent(MotionEvent event) { // 獲取觸碰的座標點 currentX = event.getX(); currentY = event.getY(); // 重新繪製小球 this.invalidate(); // 返回true表明處理方法已經處理完該事件 return true; } }
然後再xml檔案中加入自定義元件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 使用自定義元件 --> <com.example.acitvitytest.ui.DrawViewPlus android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
在Activity中只需要簡單地載入介面就行,所有的邏輯都在自定義元件中編寫,這樣可以讓程式結構更加清晰。
public class DrawActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.draw); } }
響應系統設定的事件
Configuration類專門用於描述手機裝置上的設定資訊,這些設定資訊既包括使用者特定的設定項,也包括系統的動態裝置設定。
<?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"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="35pt" android:id="@+id/ori" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="35pt" android:id="@+id/navigation" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="35pt" android:id="@+id/touch" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="35pt" android:id="@+id/mnc" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/bn" android:textSize="35pt" android:text="獲取手機資訊"/> </LinearLayout>
public class CfgActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.configuration); // 獲取應用介面中的介面元件 EditText ori = findViewById(R.id.ori); EditText navigation = findViewById(R.id.navigation); EditText touch = findViewById(R.id.touch); EditText mnc = findViewById(R.id.mnc); Button bn = findViewById(R.id.bn); // 為按鈕繫結事件監聽器 bn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 獲取系統的Configuration物件 Configuration cfg = getResources().getConfiguration(); String screen = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? "橫向螢幕" : "縱向螢幕"; String mncCode = cfg.mnc + ""; String naviname; if(cfg.orientation == Configuration.NAVIGATION_NONAV){ naviname = "沒有方向控制"; } else if (cfg.orientation == Configuration.NAVIGATION_WHEEL){ naviname = "滾輪控制方向"; } else if (cfg.orientation == Configuration.NAVIGATION_DPAD){ naviname = "方向鍵控制方向"; } else { naviname = "軌跡球控制方向"; } String touchname = cfg.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH ? "無觸控式螢幕" : "支援觸控式螢幕"; ori.setText(screen); navigation.setText(naviname); touch.setText(touchname); mnc.setText(mncCode); } }); } }
點選按鈕就可以獲取相應的設定資訊
Handler訊息傳遞機制
Handler類的主要作用有兩個:在新啟動的執行緒中傳送訊息和在主執行緒中獲取處理訊息。
我們可以通過一個新執行緒來週期性地修改ImageView所顯示的圖片,通過這種方式來開發一個動畫效果。
<?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"> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/show"/> </LinearLayout>
public class HanlderActivity extends Activity { // 定義週期性顯示圖片的id int[] images = new int[] { R.drawable.ic_launcher_foreground, R.drawable.ic_launcher_background }; int currentImageId = 0; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.handler); ImageView image = findViewById(R.id.show); Handler myhandler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); // 如果訊息是本程式傳送的,那麼就修改ImageView顯示的圖片 if(msg.what == 0x1233){ image.setImageResource(images[currentImageId++ % images.length]); } } }; // 定義一個計時器,讓該計時器週期性地執行指定任務 new Timer().schedule(new TimerTask() { @Override public void run() { // 傳送空訊息 myhandler.sendEmptyMessage(0x1233); } },0, 1200); } }
上述程式碼中使用TimerTask物件啟動了一個新的執行緒,由於啟動的執行緒沒有辦法直接存取Activity中的介面元件,因此使用Handler傳遞訊息,從而實現間接存取。程式會週期性地變換顯示的圖片
和Handler一起工作的元件有三個:
Message:Hanlder接收和處理的訊息物件
Looper:每個執行緒只能擁有一個Looper,他的loop方法負責讀取MessageQueue中的訊息,讀到資訊之後就把訊息交給傳送該訊息的Handler進行處理
MessageQueue:訊息佇列,他用先進先出的方式來管理Message
我們通常會將比較耗時的操作放到一個新的執行緒中去執行,如果使用UI執行緒執行耗時操作,那麼執行緒很可能被阻塞,從而降低使用者體驗。
我們可以看一下Looper物件中的prepare()方法
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #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容器中,並且容器中只能有一個Looper物件,假如在範例化前就已經存在了Looper物件,那麼就拋異常。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
我們可以看到Looper物件的構造方法是用private修飾的,也就是說我們不能自己範例化Looper物件,只能通過呼叫靜態的prepare()方法進行構造。
最後構造得到的範例物件是放到ThreadLocal容器中的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Looper物件最重要的方法就是loop(),該方法會反覆檢查MessageQueue中是否有訊息,如果有訊息就會取出來進行處理,如果沒有訊息就會進行阻塞,直到取出訊息為止
/** * Poll and deliver single message, return true if the outer loop should continue. */ @SuppressWarnings("AndroidFrameworkBinderIdentity") private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) { Message msg = me.mQueue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return false; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } // Make sure the observer won't change while processing a transaction. final Observer observer = sObserver; final long traceTag = me.mTraceTag; long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; if (thresholdOverride > 0) { slowDispatchThresholdMs = thresholdOverride; slowDeliveryThresholdMs = thresholdOverride; } final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0); final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); final boolean needStartTime = logSlowDelivery || logSlowDispatch; final boolean needEndTime = logSlowDispatch; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; Object token = null; if (observer != null) { token = observer.messageDispatchStarting(); } long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid); try { msg.target.dispatchMessage(msg); if (observer != null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { if (observer != null) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logSlowDelivery) { if (me.mSlowDeliveryDetected) { if ((dispatchStart - msg.when) <= 10) { Slog.w(TAG, "Drained"); me.mSlowDeliveryDetected = false; } } else { if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) { // Once we write a slow delivery log, suppress until the queue drains. me.mSlowDeliveryDetected = true; } } } if (logSlowDispatch) { showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); return true; } /** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ @SuppressWarnings("AndroidFrameworkBinderIdentity") public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } if (me.mInLoop) { Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed."); } me.mInLoop = true; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); // Allow overriding a threshold with a system prop. e.g. // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start' final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0); me.mSlowDeliveryDetected = false; for (;;) { if (!loopOnce(me, ident, thresholdOverride)) { return; } } }
下面我們就寫一個簡單的程式計算到某個指定數為止的所有質數,並且用Toast顯示出來。
介面程式碼比較簡單,就是一個文字方塊和一個按鈕
<?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"> <EditText android:id="@+id/input" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="50pt"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="點我進行計算" android:textSize="25pt" android:id="@+id/cal" android:onClick="cal"/> </LinearLayout>
在Java程式碼中我們需要定義一個執行緒,裡面定義一個Handler類,該Handler類的處理訊息的邏輯是先從訊息中取出資料,然後進行計算,最後使用Toast顯示計算結果。
按鈕的點選事件的處理邏輯是,首先封裝一個Message物件,然後將Message物件傳遞給執行緒中的Handler物件。
public class CalNumActivity extends Activity { private final String UPPER = "UPPER_NUM"; private CalThread calThread; private EditText editText; class CalThread extends Thread{ public Handler mHandler; @Override public void run() { // 範例化Looper物件 Looper.prepare(); mHandler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == 0x123){ // 獲取最大的那個數,從Message中取出Data // 該Data是Bundle物件,採用鍵值對的形式傳遞資料 int upper = msg.getData().getInt(UPPER); List<Integer> numlist = new ArrayList<>(); outer: for(int i=2; i<=upper; i++){ for(int j=2; j<=Math.sqrt(i); j++){ // 只能被1和它本身整除的才是質數 if(j == i){ continue; } if(j % i == 0){ continue outer; } } numlist.add(i); } // 顯示計算出來的質數 Toast.makeText(CalNumActivity.this, numlist.toString(), Toast.LENGTH_LONG).show(); } } }; // 執行loop()方法,從MessageQueue中取出訊息 Looper.loop(); } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.calnum); editText = findViewById(R.id.input); Button button = findViewById(R.id.cal); // 啟動新執行緒 calThread = new CalThread(); calThread.start(); } // 為按鈕的點選事件新增事件處理常式 public void cal(View source){ // 構建訊息 Message msg = new Message(); msg.what = 0x123; Bundle bundle = new Bundle(); bundle.putInt(UPPER, Integer.parseInt(editText.getText().toString())); msg.setData(bundle); calThread.mHandler.sendMessage(msg); } }
到此這篇關於Android事件處理的兩種方式詳解的文章就介紹到這了,更多相關Android事件處理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45