Android開(kāi)發(fā)事件處理的代碼如何寫(xiě)手摸手教程
正文
經(jīng)過(guò)事件分發(fā)之View事件處理和ViewGroup事件分發(fā)和處理源碼分析這兩篇的的理論知識(shí)分析,我們已經(jīng)大致的了解了事件的分發(fā)處理機(jī)制,但是這并不代表你就一定能寫(xiě)好事件處理的代碼。
既然我們有了基本功,那么本文就通過(guò)一個(gè)案例來(lái)逐步分析事件處理的代碼如何寫(xiě),事件沖突如何解決。
剖析事件分發(fā)的過(guò)程
為了模擬實(shí)際情況,我特意搞了一幅畫(huà)View各種嵌套的圖
圖中有一個(gè)MyViewGroup
,它可以左右滑動(dòng),本文就用它來(lái)講解事件處理的代碼如何寫(xiě)。
后面的分析需要大家有前面兩篇文章的基礎(chǔ),請(qǐng)務(wù)必理解清楚,否則你可能會(huì)覺(jué)得我在講天書(shū)。
ACTION_DOWN
由于我們操作的目標(biāo)是MyViewGroup
,因此我會(huì)把手指在MyViewGroup
內(nèi)容區(qū)域內(nèi)按下,至于按在哪里,其實(shí)無(wú)所謂,甚至在TextView
上也行。此時(shí)系統(tǒng)會(huì)把ACTION_DOWN
事件經(jīng)過(guò)Activity傳遞給ViewGroup0
,那么問(wèn)題來(lái)了ViewGroup0
會(huì)不會(huì)截?cái)嗍录兀?/p>
如果ViewGroup0
截?cái)嗔?code>ACTION_DOWN事件,那么它的所有子View在這個(gè)事件序列結(jié)束前,將無(wú)法接收到任何事件,包括ACTION_DOWN
事件。MyViewGroup
就是ViewGroup0
的子View,很顯然我們并不希望這樣的事情發(fā)生。如果真的發(fā)生從一開(kāi)始就截?cái)?code>ACTION_DOWN這樣的事情,那父View控件的代碼寫(xiě)的絕壁有問(wèn)題。
事件序列是由ACTION_DOWN
開(kāi)始,由ACTION_UP
或者ACTION_CANCEL
結(jié)束,并且中間有0個(gè)或者多個(gè)ACTION_MOVE
組成。
那么有沒(méi)有截?cái)?code>ACTION_DOWN事件的情況呢?當(dāng)然有,ViewGroup
必須處于一個(gè)合理的狀態(tài),并且有理由截?cái)?code>ACTION_DOWN事件。例如ViewPager
,當(dāng)手指在屏幕快速劃過(guò)后,頁(yè)面還處于滑動(dòng)狀態(tài),此時(shí)如果手指再次按下,ViewPager
把這個(gè)ACTION_DOWN
事件當(dāng)做是停止滑動(dòng)當(dāng)前滑動(dòng)并且重新開(kāi)始滑動(dòng)的指示,因此它有理由截?cái)噙@個(gè)ACTION_DOWN
事件。
那么,ViewGroup0
在沒(méi)有任何合理狀態(tài),并且還沒(méi)有任何合理理由的情況下,是絕不會(huì)截?cái)?code>ACTION_DOWN事件的,因此它會(huì)把這個(gè)事件傳遞給MyViewGroup
。
MyViewGroup
很高興接收到了第一個(gè)事件ACTION_DOWN
,按照剛才講的規(guī)則,常規(guī)狀態(tài)下,是不截?cái)?code>ACTION_DOWN事件的,但是如果MyViewGroup
在滑動(dòng)狀態(tài)中,并且手指已經(jīng)離開(kāi)屏幕,當(dāng)再次按下手指的時(shí)候,我希望MyViewGroup
截?cái)?code>ACTION_DOWN事件的,因此onInterceptTouchEvent()
方法的事件處理的框架代碼應(yīng)該這樣寫(xiě)
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: // 1. 如果處于無(wú)狀態(tài),默認(rèn)不截?cái)? // 2. 如果處于滑動(dòng)狀態(tài),截?cái)嗍录? break; } return super.onInterceptTouchEvent(ev); }
現(xiàn)在討論的是事件處理的框架代碼如何寫(xiě),因此沒(méi)有具體的代碼。
你肯定以為ACTION_DOWN
事件就這樣處理完了是吧,機(jī)智的我早已看穿一切
MyViewGroup
是需要實(shí)現(xiàn)滑動(dòng)特性的,那么它就必須要能接收到ACTION_MOVE
事件。那么ACTION_DOWN
事件要如何處理,才能確保這個(gè)事情呢?必須滿足下面的一個(gè)條件
MyViewGroup
有一個(gè)子View處理了ACTION_DOWN
事件。MyViewGroup
自己處理ACTION_DOWN
事件。
第一個(gè)條件呢,是最理想的情況,因?yàn)?code>MyViewGroup在這種情況下,不用處理ACTION_DOWN
事件就可以接收到ACTION_MOVE
事件。
然而第一個(gè)條件,是不可控的,因此我們要做好最壞的打算,那就是MyViewGroup
自己處理ACTION_DOWN
。因此,在onTouchEvent()
中處理ACTION_DOWN
事件要返回true
。
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 自己處理ACTIOND_DOWN,必須返回true return true; } return super.onTouchEvent(event); }
ACTION_MOVE
前面處理ACTION_DOWN
已經(jīng)確保了ACTION_MOVE
可以順利接收,根據(jù)前面列出的2個(gè)保證條件,那么接收ACTION_MOVE
的情況如下
MyViewGroup
有一個(gè)子View處理了ACTION_DOWN
,那么ACTION_MOVE
將會(huì)在onInterceptTouchEvent()
中被接收。MyViewGroup
自己處理了ACTION_DOWN
,那么ACTION_MOVE
將會(huì)在onTouchEvent()
中接收到。
對(duì)于第一種情況,其實(shí)有個(gè)限制條件,那就是子View必須允許MyViewGroup
截?cái)嗍录駝tMyViewGroup
將收不到ACTION_MOVE
事件。如果出現(xiàn)這種情況,那你得檢查子控件的代碼了是否寫(xiě)的合理了。
首先討論第二種情況,如果ACTION_MOVE
在onTouchEvent()
中接收到,那就代表MyViewGroup
要自己處理事件來(lái)滑動(dòng),因此返回true
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: // 自己處理ACTION_MOVE,返回true return true; } return super.onTouchEvent(event); }
現(xiàn)在來(lái)繼續(xù)看第一種情況,ACTION_MOVE
在發(fā)送給處理了ACTION_DOWN
的子View前,需要通過(guò)MyViewGroup
的onInterceptTouchEvent()
方法,那么MyViewGroup
要不要截?cái)?code>ACTION_MOVE事件呢?其實(shí)有很多種情況,我們來(lái)逐一分析可行性。
有人說(shuō),既然onInterceptTouchEvent()
會(huì)一直接收ACTION_MOVE
事件,那可以不截?cái)嗑椭苯訄?zhí)行滑動(dòng)。表面上看MyViewGroup
實(shí)現(xiàn)了滑動(dòng),但是在實(shí)際中可能遇到問(wèn)題。假如子View也是一個(gè)滑動(dòng)的控件,那么在MyViewGroup
滑動(dòng)的時(shí)候,由于沒(méi)有截?cái)嗍录?,因此子View同時(shí)也會(huì)根據(jù)自己的意愿去滑動(dòng),這豈不是瞎搞嗎?又或者說(shuō)子View在接收ACTION_MOVE
事件后,請(qǐng)求父View不允許截?cái)嗪罄m(xù)的事件,那么MyViewGroup
后續(xù)就處理不了ACTION_MOVE
事件了。
經(jīng)過(guò)上面的分析,有人可能會(huì)說(shuō),一不做二不休,那就直接截?cái)嗟昧恕N抑荒苷f(shuō),這位施主你太沖動(dòng)!
如果直接粗暴的截?cái)?,萬(wàn)一遇上了不是完全垂直滑動(dòng)的手勢(shì),MyViewGroup
卻在水平滑動(dòng),那豈不是尷尬了。
這時(shí)候,肯定有人忍不了了,截?cái)嘁膊皇?,不截?cái)嘁膊皇?,你想鬧哪樣!我們可以變通下嘛,我們要有條件的截?cái)?,避免剛才的尷尬情況嘛,舉兩個(gè)常用的條件
- 達(dá)到滑動(dòng)的臨界點(diǎn)
- 判斷手勢(shì)是水平滑動(dòng)還是垂直滑動(dòng)
那么,在onInterceptTouchEvent()
方法中關(guān)于是否截?cái)?code>ACTION_MOVE的框架代碼可以這樣寫(xiě)
public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_MOVE: // 達(dá)到滑動(dòng)標(biāo)準(zhǔn)就截?cái)?,否則不截?cái)? // 滑動(dòng)標(biāo)準(zhǔn)如下 // 1. 達(dá)到滑動(dòng)的臨界距離 // 2. 判斷手勢(shì)是水平滑動(dòng) break; } return super.onInterceptTouchEvent(ev); }
ACTION_UP
我們先來(lái)討論下,ACTION_UP
會(huì)在哪里接收到
MyViewGroup
處理了ACTION_DOWN
,ACTION_UP
將會(huì)在onTouchEvent()
中接收到。MyViewGroup
在截?cái)?code>ACTION_MOVE之前,ACTION_UP
將會(huì)在onInterceptTouchEvent()
中接收到。MyViewGroup
截?cái)?code>ACTION_MOVE后,ACTION_UP
將會(huì)在onTouchEvent()
中接收到。
第一種情況,返回true
吧,因?yàn)楫吘故?code>MyViewGroup自己處理了ACTION_UP
事件。
第二種情況,返回false
吧,因?yàn)榇藭r(shí)MyViewGroup
還沒(méi)有處理滑動(dòng)事件呢。
第三種情況,返回true
吧,因?yàn)楫吘故?code>MyViewGroup自己處理了ACTION_UP
事件。
從源碼角度看,對(duì)于ACTION_UP
事件的處理的返回值,好像并不太重要。 但是返回true
還是false
其實(shí)是向父View表明一個(gè)種態(tài)度,那就是我到底是不是處理了ACTION_UP
事件。
ACTION_CANCEL
從前面文章分析可知,ACTION_CANCEL
是在MyViewGroup
的父View截?cái)嗔?code>MyViewGroup的ACTION_MOVE
事件后收到的,ACTION_CANCEL
接收的地方其實(shí)和ACTION_UP
是一樣,至于是處理還是不處理,根據(jù)實(shí)際中有沒(méi)有做實(shí)質(zhì)的動(dòng)作來(lái)相應(yīng)的返回true
或者false
。
完成案例代碼
前面我們已經(jīng)對(duì)每個(gè)事件到底處不處理進(jìn)行了分析,并且寫(xiě)出了事件處理的框架,那么接下來(lái),我們就可以在這個(gè)框架之下,很放心地完成MyViewGroup
滑動(dòng)特性的代碼了。
ACTION_DOWN
在處理ACTION_DOWN
的時(shí)候要做啥呢?當(dāng)然是記錄手指按下時(shí)的坐標(biāo)。由于ACTION_DOWN
一定會(huì)經(jīng)過(guò)onInterceptTouchEvent()
,所以在這里記錄按下坐標(biāo)
public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getX(); float y = ev.getY(); switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: // 記錄手指按下的坐標(biāo) mLastX = mStartX = x; mLastY = mStartY = y; // 1. 如果處于無(wú)狀態(tài),默認(rèn)不截?cái)? // 2. 如果處于滑動(dòng)狀態(tài),截?cái)嗍录? break; } return super.onInterceptTouchEvent(ev); }
mStartX
和mStartY
表示手指按下的坐標(biāo),mLastX
和mLastY
表示最近一次事件的坐標(biāo)。
ACTION_MOVE
根據(jù)前面的分析,處理ACTION_MOVE
有情況有如下幾種
如果MyViewGroup
存在一個(gè)子View處理了ACTION_DOWN
,
MyViewGroup
截?cái)?code>ACTION_MOVE之前,ACTION_MOVE
將會(huì)在onInterceptTouchEvent()
中接收。
MyViewGroup
截?cái)?code>ACTION_MOVE之后,ACTION_MOVE
將會(huì)在onTouchEvent()
中接收。
如果MyViewGroup
處理了ACTION_DOWN
,那么ACTION_MOVE
將會(huì)在onTouchEvent()
中接收。
第一種情況,根據(jù)前面的分析,我們將在onInterceptTouchEvent()
根據(jù)條件來(lái)截?cái)?code>ACTION_MOVE事件。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getX(); float y = ev.getY(); switch (ev.getActionMasked()) { case MotionEvent.ACTION_MOVE: // 計(jì)算從手指按下時(shí)滑動(dòng)的距離 float distanceX = Math.abs(x - mStartX); float distanceY = Math.abs(y - mStartY); if (distanceX > mScaledTouchSlop && distanceX > 2 * distanceY) { // 設(shè)置拖拽狀態(tài) setState(SCROLLING_STATE_DRAGGING); // 不允許父View截?cái)嗪罄m(xù)事件 requestDisallowIntercept(true); // 執(zhí)行一次拖拽的滑動(dòng) performDrag(x); // 更新最新事件坐標(biāo) mLastX = x; mLastY = y; // 截?cái)嗪罄m(xù)的事件 return true; } break; } return super.onInterceptTouchEvent(ev); }
根據(jù)我們的分析,要達(dá)到截?cái)?code>ACTION_MOVE的標(biāo)準(zhǔn)才截?cái)嗪罄m(xù)的ACTION_MOVE
事件,從代碼中可以看出這個(gè)標(biāo)準(zhǔn)有兩條
- 水平滑動(dòng)的距離要大于一個(gè)臨界值。
- 水平滑動(dòng)的距離要大于兩倍的垂直滑動(dòng)距離,這樣就排除了一些不標(biāo)準(zhǔn)的手勢(shì)。
當(dāng)我們認(rèn)為這是一次有效的滑動(dòng)的時(shí)候,就要截?cái)嗪罄m(xù)的ACTION_MOVE
事件,這就是代碼中看到的return true
的原因。
然而事情還沒(méi)有完,我們還做了一些優(yōu)化動(dòng)作
第一步,設(shè)置拖拽狀態(tài)。這是因?yàn)樵诮財(cái)嗪罄m(xù)的ACTION_MOVE
后,后續(xù)的ACTION_MOVE
事件就會(huì)分發(fā)給MyViewGroup
的onTouchEvent()
,而onTouchEvent()
也要處理其他情況的拖拽,因此需要這個(gè)狀態(tài)判斷值。
第二步,請(qǐng)求父View不允許截?cái)嗪罄m(xù)ACTION_MOVE
事件。因?yàn)?code>MyViewGroup馬上要執(zhí)行以系列的滑動(dòng)動(dòng)作,如果父View此時(shí)截?cái)嗔耸录强隙ㄊ遣缓线m的,因此要通知父View不要搞事情。
第三步,執(zhí)行一次滑動(dòng)。可能很多人不理解為何要在onInterceptTouchEvent()
中執(zhí)行滑動(dòng)動(dòng)作,這個(gè)方法名義上只是用來(lái)判斷是否截?cái)嗍录摹?/p>
其實(shí)這里是有原因的,由于要截?cái)嗪罄m(xù)的ACTION_MOVE
事件,那么這次的ACTION_MOVE
事件是不會(huì)發(fā)送到MyViewGroup
的onTouchEvent()
中的,而是把這個(gè)ACTION_MOVE
事件變?yōu)?code>ACTION_CANCEL事件發(fā)給處理了ACTION_DOWN
事件的子View。因此當(dāng)前的ACTION_MOVE
如果不在onInterceptTouchEvent()
處理,那么就會(huì)丟失這一次滑動(dòng)處理。
截?cái)嗪罄m(xù)的ACTION_MOVE
后,MyViewGroup
的onTouchEvent()
會(huì)接收后續(xù)的ACTION_MOVE
,那么在這里要繼續(xù)執(zhí)行滑動(dòng)
public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: if (mState == SCROLLING_STATE_DRAGGING) { // 處于滑動(dòng)狀態(tài)就繼續(xù)執(zhí)行滑動(dòng) performDrag(x); mLastX = x; } return true; } return super.onTouchEvent(event); }
至此,處理ACTION_MOVE
的第一種情況已經(jīng)處理完畢,我們現(xiàn)在來(lái)看下第二種情況,那就是MyViewGroup
處理了ACTION_DOWN
,所有的ACTION_MOVE
事件都將交給MyViewGroup
的onTouchEvent()
處理。那么此時(shí)MyViewGroup
還沒(méi)有滑動(dòng),因此需要再次判斷是否達(dá)到滑動(dòng)標(biāo)準(zhǔn)
@Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: if (mState == SCROLLING_STATE_DRAGGING) { // 處于滑動(dòng)狀態(tài)就繼續(xù)執(zhí)行滑動(dòng) performDrag(x); // 更新最新坐標(biāo)點(diǎn) mLastX = x; } else { // 不處于滑動(dòng)狀態(tài),就再次檢測(cè)是否達(dá)滑動(dòng)標(biāo)準(zhǔn) float distanceX = Math.abs(x - mLastX); float distanceY = Math.abs(y - mLastY); if (distanceX > mScaledTouchSlop && distanceX > 2 * distanceY) { setState(SCROLLING_STATE_DRAGGING); requestDisallowIntercept(true); performDrag(x); mLastX = x; } } return true; } return super.onTouchEvent(event); }
ACTION_UP
對(duì)于ACTION_UP
事件,我們先來(lái)預(yù)想下發(fā)生的情況
- 沒(méi)有截?cái)?code>ACTION_MOVE事件之前,
ACTION_UP
事件會(huì)先由onInterceptTouchEvent()
處理。 - 截?cái)?code>ACTION_MOVE事件之后,
ACTION_UP
事件會(huì)由onTouchEvent()
處理。 MyViewGroup
處理了ACTION_DOWN
事件,ACTION_UP
事件全部會(huì)由onTouchEvent()
處理。
第一種情況,由于MyViewGroup
還沒(méi)有產(chǎn)生滑動(dòng),因此不需要處理此種情況下手指抬起事件。
第二種情況,MyViewGroup
已經(jīng)產(chǎn)生滑動(dòng),如果MyViewGroup
是一個(gè)像ViewPager
一樣的頁(yè)面式的滑動(dòng),那么當(dāng)手指抬起時(shí),它需要進(jìn)行一些頁(yè)面定位操作,也就是決定滑動(dòng)到哪個(gè)頁(yè)面。
第三種情況,其實(shí)就是第一種情況和第二種情況的綜合版而已。
@Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getActionMasked()) { case MotionEvent.ACTION_UP: if (mState == SCROLLING_STATE_DRAGGING) { setState(SCROLLING_STATE_SETTING); // 使用Scroller進(jìn)行定位操作 int contentWidth = getWidth() - getHorizontalPadding(); int scrollX = getScrollX(); int targetIndex = (scrollX + contentWidth / 2) / contentWidth; mScroller.startScroll(scrollX, 0, targetIndex * contentWidth - scrollX, 0); invalidate(); } return true; } return super.onTouchEvent(event); }
ACTION_CANCEL
ACTION_CANCEL
這個(gè)事件比較特殊,按照正常流程看,是由于父View截?cái)嗔?code>MyViewGroup的ACTION_MOVE
事件后,把ACTION_MOVE
變?yōu)榱?code>ACTION_CANCEL,然后發(fā)送給MyViewGroup
。
如果MyViewGroup
在進(jìn)行滑動(dòng)之前,會(huì)先請(qǐng)求父View不允許截?cái)嗨氖录?,也就是說(shuō)之后父View不可能截?cái)?code>ACTION_MOVE事件,也就是不可能發(fā)送ACTION_CANCEL
事件。
如果MyViewGroup
還沒(méi)開(kāi)始滑動(dòng),那么MyViewGroup
就可能會(huì)收到ACTION_CANCEL
事件,然而此時(shí)不用做任何處理動(dòng)作,因?yàn)?code>MyViewGroup還沒(méi)有滑動(dòng)產(chǎn)生狀態(tài)呢。
這是一種正常情況下的純理論分析,不排除異常情況。
截?cái)郃CTION_DOWN
現(xiàn)在,我們回過(guò)頭來(lái)處理MyViewGroup
截?cái)?code>ACTION_DOWN的情況,前面我們說(shuō)過(guò),如果手指抬起,MyViewGroup
還是處于滑動(dòng)狀態(tài),在我們這個(gè)例子中叫做定位狀態(tài),那么當(dāng)手指按下時(shí),就需要截?cái)嗍录驗(yàn)?code>MyViewGroup認(rèn)為這個(gè)時(shí)候的按下動(dòng)作是為了停止當(dāng)前滑動(dòng),并用手指控制滑動(dòng)
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { float x = ev.getX(); float y = ev.getY(); switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: // 重置狀態(tài) setState(SCROLLING_STATE_IDLE); // 記錄手指按下的坐標(biāo) mLastX = mStartX = x; mLastY = mStartY = y; // 1. 如果處于無(wú)狀態(tài),默認(rèn)不截?cái)? // 2. 如果處于滑動(dòng)狀態(tài),截?cái)嗍录? if (!mScroller.isFinished()) { // 停止定位動(dòng)作 mScroller.abortAnimation(); // 設(shè)置拖拽狀態(tài) setState(SCROLLING_STATE_DRAGGING); // 不允許父View截?cái)嗪罄m(xù)事件 requestDisallowIntercept(true); return true; } break; } return super.onInterceptTouchEvent(ev); }
當(dāng)MyViewGroup
截?cái)?code>ACTION_DOWN事件后,那么后續(xù)的的ACTION_MOVE
事件就由onTouchEvent()
來(lái)進(jìn)行滑動(dòng)處理,這個(gè)過(guò)程在前面已經(jīng)實(shí)現(xiàn)。
結(jié)束
本文先從理論上搭建了事件處理的框架,然后用一個(gè)簡(jiǎn)單的例子實(shí)現(xiàn)了這個(gè)框架。如果大家在看本文的時(shí)候有任何疑問(wèn),請(qǐng)先參考前面兩篇文章的分析,如果還是有疑問(wèn),歡迎在評(píng)論里留言討論。
詳細(xì)源碼請(qǐng)參考github,實(shí)現(xiàn)的效果如下
為了測(cè)試,我在第一個(gè)頁(yè)面放置了一個(gè)Button
,然后點(diǎn)擊Button
開(kāi)始滑動(dòng),可以看到Button
并沒(méi)有相應(yīng)點(diǎn)擊事件。然后在第二個(gè)頁(yè)面返回第一個(gè)頁(yè)面時(shí),只有滑動(dòng)超過(guò)了一半的寬度,才會(huì)自動(dòng)滑動(dòng)到第一頁(yè)面。
以上就是Android開(kāi)發(fā)事件處理的代碼如何寫(xiě)手摸手教程的詳細(xì)內(nèi)容,更多關(guān)于Android開(kāi)發(fā)事件處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)用戶圓形頭像和模糊背景
這篇文章主要介紹了Android實(shí)現(xiàn)用戶圓形頭像和模糊背景 ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-046步輕松實(shí)現(xiàn)兩個(gè)listView聯(lián)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了教大家通過(guò)6步輕松實(shí)現(xiàn)兩個(gè)listView聯(lián)動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04解析:ClickOnce通過(guò)URL傳遞參數(shù) XXX.application?a=1
本篇文章是對(duì)ClickOnce通過(guò)URL傳遞參數(shù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06Android 中ViewPager中使用WebView的注意事項(xiàng)
這篇文章主要介紹了Android 中ViewPager中使用WebView的注意事項(xiàng)的相關(guān)資料,希望通過(guò)本文大家在使用過(guò)程中遇到這樣的問(wèn)題解決,需要的朋友可以參考下2017-09-09Android中FlowLayout組件實(shí)現(xiàn)瀑布流效果
大家好,本篇文章主要講的是Android中FlowLayout組件實(shí)現(xiàn)瀑布流效果,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01利用Jetpack Compose實(shí)現(xiàn)經(jīng)典俄羅斯方塊游戲
你的童年是否有俄羅斯方塊呢,本文就來(lái)介紹如何通過(guò)Jetpack Compose實(shí)現(xiàn)一個(gè)俄羅斯方塊!感興趣的小伙伴快跟隨小編一起動(dòng)手嘗試一下吧2022-05-05FFmpeg Principle學(xué)習(xí)open_output_file打開(kāi)輸出文件
這篇文章主要為大家介紹了FFmpeg Principle學(xué)習(xí)open_output_file打開(kāi)輸出文件示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Android selector狀態(tài)選擇器的使用詳解
這篇文章主要為大家詳細(xì)介紹了Android selector狀態(tài)選擇器的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09