您好,登錄后才能下訂單哦!
這篇文章主要講解了“Android事件處理的兩種方式是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Android事件處理的兩種方式是什么”吧!
安卓提供了兩種方式的事件處理:基于回調(diào)的事件處理和基于監(jiān)聽(tīng)的事件處理。
基于監(jiān)聽(tīng)的事件處理
基于監(jiān)聽(tīng)的事件處理一般包含三個(gè)要素,分別是:
Event Source(事件源):事件發(fā)生的場(chǎng)所,通常是各個(gè)組件
Event(事件):事件封裝了界面組件上發(fā)生的特定事件(通常就是用戶(hù)的一次操作)
Event Listener(事件監(jiān)聽(tīng)器):負(fù)責(zé)監(jiān)聽(tīng)事件源發(fā)生的事件,并對(duì)各種事件作出相應(yīng)的響應(yīng)
下面使用一個(gè)簡(jiǎn)單的案例介紹按鈕事件監(jiān)聽(tīng)器
布局文件就是簡(jiǎn)單的線(xiàn)性布局器,上面是一個(gè)EditText,下面是一個(gè)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注冊(cè)一個(gè)事件監(jiān)聽(tīng)器
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按鈕被單擊了!"); } }); } }
單擊按鈕后,文本框就會(huì)顯示"bn按鈕被單擊了!"
外部類(lèi)作為事件監(jiān)聽(tīng)器類(lèi)
如果某個(gè)事件監(jiān)聽(tīng)器確實(shí)需要被多個(gè)GUI界面所共享,而且主要是完成某種業(yè)務(wù)邏輯的實(shí)現(xiàn),那么就可以考慮使用外部類(lèi)形式來(lái)定義事件監(jiān)聽(tīng)器類(lèi)。
我們定義一個(gè)類(lèi)實(shí)現(xiàn)OnClickListener接口,并且實(shí)現(xiàn)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(); // 創(chuàng)建發(fā)送短信想PendingIntent PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE); // 發(fā)送短信文本 smsManager.sendTextMessage(addressStr, null, contentStr, pendingIntent, null); Toast.makeText(activity, "短信發(fā)送完成", Toast.LENGTH_LONG).show(); } }
然后編輯一個(gè)簡(jiǎn)單的線(xiàn)性布局,有兩個(gè)輸入框和一個(gè)按鈕
<?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="點(diǎn)擊我發(fā)送信息" android:longClickable="true"/> </LinearLayout>
最后編寫(xiě)一個(gè)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)); // 請(qǐng)求權(quán)限 ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.SEND_SMS} , 1); } }
為了能夠順利發(fā)送短信,需要重新開(kāi)啟一臺(tái)模擬器,填寫(xiě)模擬器的ID,否則程序會(huì)報(bào)錯(cuò)
我的電腦配置沒(méi)有辦法同時(shí)運(yùn)行兩個(gè)虛擬機(jī),所以這里就不展示了。
基于回調(diào)的事件處理
回調(diào)這個(gè)詞在編程領(lǐng)域經(jīng)常被提及,我的理解是,回調(diào)實(shí)際上是某個(gè)類(lèi)中早已經(jīng)定義好的方法或者接口,當(dāng)我們繼承或者實(shí)現(xiàn)接口的時(shí)候,可以相應(yīng)地重寫(xiě)對(duì)應(yīng)方法,或者實(shí)現(xiàn)相應(yīng)接口。在程序運(yùn)行的特定位置會(huì)調(diào)用特定的方法,當(dāng)我們重寫(xiě)了某個(gè)方法之后,就可以在特定情況下實(shí)現(xiàn)對(duì)應(yīng)的邏輯。
最最簡(jiǎn)單的一個(gè)例子就是Activity的onCreate()方法,當(dāng)我們初始化一個(gè)Activity類(lèi)的時(shí)候,就會(huì)調(diào)用這個(gè)方法,如果我們不重寫(xiě)這個(gè)方法,那么程序就會(huì)調(diào)用默認(rèn)的onCreate()方法,如果我們重寫(xiě)了這個(gè)方法,那么程序就會(huì)調(diào)用我們重寫(xiě)的onCreate()方法。
我們可以用回調(diào)的方式實(shí)現(xiàn)一個(gè)跟隨手指的小球。
首先自定義一個(gè)自定義的View
public class DrawViewPlus extends View { public float currentX = 50; public float currentY = 50; // 定義創(chuàng)建畫(huà)筆 Paint p = new Paint(); public DrawViewPlus(Context context, AttributeSet set) { super(context, set); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 設(shè)置畫(huà)筆顏色 p.setColor(Color.RED); // 繪制小球 canvas.drawCircle(currentX, currentY, 15, p); } @Override public boolean onTouchEvent(MotionEvent event) { // 獲取觸碰的坐標(biāo)點(diǎn) currentX = event.getX(); currentY = event.getY(); // 重新繪制小球 this.invalidate(); // 返回true表明處理方法已經(jīng)處理完該事件 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中只需要簡(jiǎn)單地加載界面就行,所有的邏輯都在自定義組件中編寫(xiě),這樣可以讓程序結(jié)構(gòu)更加清晰。
public class DrawActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.draw); } }
響應(yīng)系統(tǒng)設(shè)置的事件
Configuration類(lèi)專(zhuān)門(mén)用于描述手機(jī)設(shè)備上的配置信息,這些配置信息既包括用戶(hù)特定的配置項(xiàng),也包括系統(tǒng)的動(dòng)態(tài)設(shè)備配置。
<?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="獲取手機(jī)信息"/> </LinearLayout>
public class CfgActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.configuration); // 獲取應(yīng)用界面中的界面組件 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); // 為按鈕綁定事件監(jiān)聽(tīng)器 bn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 獲取系統(tǒng)的Configuration對(duì)象 Configuration cfg = getResources().getConfiguration(); String screen = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? "橫向屏幕" : "縱向屏幕"; String mncCode = cfg.mnc + ""; String naviname; if(cfg.orientation == Configuration.NAVIGATION_NONAV){ naviname = "沒(méi)有方向控制"; } 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 ? "無(wú)觸摸屏" : "支持觸摸屏"; ori.setText(screen); navigation.setText(naviname); touch.setText(touchname); mnc.setText(mncCode); } }); } }
點(diǎn)擊按鈕就可以獲取相應(yīng)的配置信息
Handler消息傳遞機(jī)制
Handler類(lèi)的主要作用有兩個(gè):在新啟動(dòng)的線(xiàn)程中發(fā)送消息和在主線(xiàn)程中獲取處理消息。
我們可以通過(guò)一個(gè)新線(xiàn)程來(lái)周期性地修改ImageView所顯示的圖片,通過(guò)這種方式來(lái)開(kāi)發(fā)一個(gè)動(dòng)畫(huà)效果。
<?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); // 如果消息是本程序發(fā)送的,那么就修改ImageView顯示的圖片 if(msg.what == 0x1233){ image.setImageResource(images[currentImageId++ % images.length]); } } }; // 定義一個(gè)計(jì)時(shí)器,讓該計(jì)時(shí)器周期性地執(zhí)行指定任務(wù) new Timer().schedule(new TimerTask() { @Override public void run() { // 發(fā)送空消息 myhandler.sendEmptyMessage(0x1233); } },0, 1200); } }
上述代碼中使用TimerTask對(duì)象啟動(dòng)了一個(gè)新的線(xiàn)程,由于啟動(dòng)的線(xiàn)程沒(méi)有辦法直接訪問(wèn)Activity中的界面組件,因此使用Handler傳遞消息,從而實(shí)現(xiàn)間接訪問(wèn)。程序會(huì)周期性地變換顯示的圖片
和Handler一起工作的組件有三個(gè):
Message:Hanlder接收和處理的消息對(duì)象
Looper:每個(gè)線(xiàn)程只能擁有一個(gè)Looper,他的loop方法負(fù)責(zé)讀取MessageQueue中的消息,讀到信息之后就把消息交給發(fā)送該消息的Handler進(jìn)行處理
MessageQueue:消息隊(duì)列,他用先進(jìn)先出的方式來(lái)管理Message
我們通常會(huì)將比較耗時(shí)的操作放到一個(gè)新的線(xiàn)程中去執(zhí)行,如果使用UI線(xiàn)程執(zhí)行耗時(shí)操作,那么線(xiàn)程很可能被阻塞,從而降低用戶(hù)體驗(yàn)。
我們可以看一下Looper對(duì)象中的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)); }
這是一個(gè)靜態(tài)方法,大概的邏輯就是實(shí)例化一個(gè)Looper對(duì)象放到sThreadLocal容器中,并且容器中只能有一個(gè)Looper對(duì)象,假如在實(shí)例化前就已經(jīng)存在了Looper對(duì)象,那么就拋異常。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
我們可以看到Looper對(duì)象的構(gòu)造方法是用private修飾的,也就是說(shuō)我們不能自己實(shí)例化Looper對(duì)象,只能通過(guò)調(diào)用靜態(tài)的prepare()方法進(jìn)行構(gòu)造。
最后構(gòu)造得到的實(shí)例對(duì)象是放到ThreadLocal容器中的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Looper對(duì)象最重要的方法就是loop(),該方法會(huì)反復(fù)檢查MessageQueue中是否有消息,如果有消息就會(huì)取出來(lái)進(jìn)行處理,如果沒(méi)有消息就會(huì)進(jìn)行阻塞,直到取出消息為止
/** * 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; } } }
下面我們就寫(xiě)一個(gè)簡(jiǎn)單的程序計(jì)算到某個(gè)指定數(shù)為止的所有質(zhì)數(shù),并且用Toast顯示出來(lái)。
界面代碼比較簡(jiǎn)單,就是一個(gè)文本框和一個(gè)按鈕
<?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="點(diǎn)我進(jìn)行計(jì)算" android:textSize="25pt" android:id="@+id/cal" android:onClick="cal"/> </LinearLayout>
在Java代碼中我們需要定義一個(gè)線(xiàn)程,里面定義一個(gè)Handler類(lèi),該Handler類(lèi)的處理消息的邏輯是先從消息中取出數(shù)據(jù),然后進(jìn)行計(jì)算,最后使用Toast顯示計(jì)算結(jié)果。
按鈕的點(diǎn)擊事件的處理邏輯是,首先封裝一個(gè)Message對(duì)象,然后將Message對(duì)象傳遞給線(xiàn)程中的Handler對(duì)象。
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() { // 實(shí)例化Looper對(duì)象 Looper.prepare(); mHandler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == 0x123){ // 獲取最大的那個(gè)數(shù),從Message中取出Data // 該Data是Bundle對(duì)象,采用鍵值對(duì)的形式傳遞數(shù)據(jù) 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和它本身整除的才是質(zhì)數(shù) if(j == i){ continue; } if(j % i == 0){ continue outer; } } numlist.add(i); } // 顯示計(jì)算出來(lái)的質(zhì)數(shù) Toast.makeText(CalNumActivity.this, numlist.toString(), Toast.LENGTH_LONG).show(); } } }; // 執(zhí)行l(wèi)oop()方法,從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); // 啟動(dòng)新線(xiàn)程 calThread = new CalThread(); calThread.start(); } // 為按鈕的點(diǎn)擊事件添加事件處理函數(shù) public void cal(View source){ // 構(gòu)建消息 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事件處理的兩種方式是什么”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Android事件處理的兩種方式是什么這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。