Android開(kāi)心消消樂(lè)代碼實(shí)例詳解
突然想要在android上寫(xiě)一個(gè)消消樂(lè)的代碼,在此之前沒(méi)有系統(tǒng)地學(xué)過(guò)java的面向?qū)ο?,也沒(méi)有任何android相關(guān)知識(shí),不過(guò)還是會(huì)一點(diǎn)C++。8月初開(kāi)始搭建環(huán)境,在這上面花了相當(dāng)多的時(shí)間,然后看了一些視頻和電子書(shū),對(duì)android有了一個(gè)大概的了解,感覺(jué)差不多了的時(shí)候就開(kāi)始寫(xiě)了。
瘋狂地查閱各種資料,反反復(fù)復(fù)了好幾天后,也算是寫(xiě)出了個(gè)成品。原計(jì)劃有很多地方還是可以繼續(xù)寫(xiě)下去的,比如UI設(shè)計(jì),比如動(dòng)畫(huà)特效,時(shí)間設(shè)計(jì),關(guān)卡設(shè)計(jì),以及與數(shù)據(jù)庫(kù)的連接,如果可以的話還能寫(xiě)個(gè)聯(lián)網(wǎng)功能,當(dāng)然因?yàn)閷?xiě)到后期內(nèi)心感到格外的疲倦,所以以上內(nèi)容全部被砍掉了。
所以最后的成果就只有這樣了……因?yàn)橥耆珱](méi)有設(shè)計(jì)過(guò)所以非常難看,功能也只有消方塊而已,圖片還是從之前寫(xiě)的連連看里搬過(guò)來(lái)的。反正就算一個(gè)小的demo好了。
布局
一開(kāi)始設(shè)計(jì)的布局是xml里的,但是后來(lái)想了想有64個(gè)按鈕這樣復(fù)制下去實(shí)在是太不好了,所以就把按鈕那一部分用代碼布局了。
private void initBtn(int i,int j) { btn[8*i+j].setBackgroundDrawable (this.getResources().getDrawable(getStyle())); point p = new point(i,j,btn[8*i+j],id); btn[8*i+j].setTag(p); map[i][j] = num; } //…… LinearLayout vlayout = (LinearLayout)findViewById(R.id.vlayout); TableLayout tlayout = new TableLayout(this); TableRow row[] = new TableRow[8]; for(int i=0;i<8;i++){ row[i] = new TableRow(this); row[i].setGravity(Gravity.CENTER); for(int j=0;j<8;j++){ btn[8*i+j] = new ImageButton(this); btn[8*i+j].setLayoutParams(new TableRow.LayoutParams(38,38)); initBtn(i,j); btn[8*i+j].setOnClickListener(listener); row[i].addView(btn[8*i+j]); } tlayout.addView(row[i]); } //……
其中g(shù)etStyle()函數(shù)返回了按鈕的樣式id,該id是隨機(jī)生成的。
point p存儲(chǔ)了關(guān)于按鈕的信息,它在按鈕點(diǎn)擊事件中會(huì)被使用。
android中的按鈕有三種狀態(tài):點(diǎn)擊態(tài)、普通態(tài)、焦點(diǎn)態(tài)。最后一個(gè)的意思是用戶(hù)按方向鍵盤(pán)(或類(lèi)似功能鍵)來(lái)控制哪個(gè)按鈕處于焦點(diǎn),這樣就可以不通過(guò)鼠標(biāo)對(duì)按鈕進(jìn)行操作。當(dāng)然在這里并沒(méi)有用到這個(gè)狀態(tài),只用到了前面兩種,但是因?yàn)橐呀?jīng)有現(xiàn)成圖片了,就把三種狀態(tài)都寫(xiě)入了btn?.xml(放在drawable文件夾下),使用的時(shí)候直接引用這個(gè)xml文件就可以了。
btn1.xml:
<?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/a1_2" android:state_pressed="true"/> <item android:drawable="@drawable/a1" android:state_focused="false" android:state_pressed="false"/> <item android:drawable="@drawable/a1_1" android:state_focused="true"/> <item android:drawable="@drawable/a1" android:state_focused="false"/> </selector>
判斷是否可消去
在點(diǎn)擊事件中,用兩個(gè)變量記錄先后點(diǎn)擊的按鈕信息,這兩個(gè)變量是滾動(dòng)使用的,也就是說(shuō),第一次點(diǎn)擊存入變量1,第二次點(diǎn)擊存入變量2,第三次點(diǎn)擊存入變量1,第四次點(diǎn)擊存入變量2……額外有一個(gè)布爾變來(lái)控制信息存入哪個(gè)變量。
每次點(diǎn)擊后,都計(jì)算先后兩次點(diǎn)擊的按鈕是否相鄰。如果相鄰,那么交換它們的位置。
我們用二維數(shù)組mark來(lái)記錄每個(gè)方塊是否可以消去,初始化為0。交換后,先一行行地掃描一遍,檢查是否有相連的三個(gè)以上圖案相同的按鈕,如果有的話,把它們都做上標(biāo)記。然后再一列列掃描一遍,同樣檢查是否有相連的三個(gè)以上圖案相同的按鈕。總體復(fù)雜度為O(2*n^2)
當(dāng)然可以有更快的算法,但是在這個(gè)代碼里,我們對(duì)計(jì)算的速度沒(méi)有要求,甚至希望它慢一些(因?yàn)閯?dòng)畫(huà)效果需要在這里停頓一下),所以就沒(méi)有必要進(jìn)行優(yōu)化了。
那么mark里存儲(chǔ)些什么呢?一開(kāi)始我把mark設(shè)計(jì)為布爾量,可消去的設(shè)為true。后來(lái)寫(xiě)到消去后更新按鈕后,我突然想到mark里不僅僅可以記錄是否可消去,還可以記錄消去后應(yīng)該更新為什么。
我們知道,消去一行按鈕后,上面的按鈕會(huì)掉下來(lái)補(bǔ)充空位,也就是說(shuō)消去的這一行會(huì)被上面一行取代。所以我們把這些按鈕的mark賦值為1。
同樣,消去一列n個(gè)按鈕后,每個(gè)按鈕會(huì)被它上面第n個(gè)按鈕取代,所以我們把這些按鈕的mark賦值為n。
這樣,在更新的時(shí)候,我們只需要遍歷二維數(shù)組mark,根據(jù)它對(duì)應(yīng)的值,來(lái)采取相應(yīng)的操作就可以了。
在這里,要注意順序性的問(wèn)題,首先,在賦值mark的時(shí)候,應(yīng)該先掃描橫行,再掃描豎列,因?yàn)榭赡軙?huì)出現(xiàn)十字架或T字型的消去,這里橫縱有重疊,而更新的時(shí)候以縱為準(zhǔn),所以后掃描縱列來(lái)覆蓋橫行的值。
另外一個(gè)要注意的順序,就是在更新的時(shí)候,一定要從上往下掃描。如果從下往上掃描的話,下面圖案的更新可能會(huì)破壞到上面的圖,那么,上面原來(lái)存在的可以被消去的方塊就已經(jīng)被破壞了,但是mark還記錄著消去方塊的索引,這樣就會(huì)引起錯(cuò)的消去。反之,上面圖案的消去是不會(huì)破壞下面的圖案的。
判斷地圖是否還存在解
每一輪消去后,我們都需要判斷地圖上是否還存在解,如果不存在,就要進(jìn)行更新
因?yàn)閮H僅是判斷存在性,算法略有變化。
我們需要考慮到所有的交換情況,以及它們能否產(chǎn)生可行解,一旦找到一個(gè),我們就可以返回true。所以外循環(huán)包含兩個(gè),一個(gè)掃描橫向相鄰的所有方塊,一個(gè)掃描縱向相鄰的所有方塊。
對(duì)于特定的兩個(gè)方塊,我們先交換它們,然后對(duì)于兩個(gè)方塊,都計(jì)算它們的十字架區(qū)域是否存在可行解,之后再把它們交換回來(lái):
(十字架區(qū)域)
總體的最壞時(shí)間復(fù)雜度是2*(n^2)*2*8。
多線程
在主線程里,按理來(lái)說(shuō)應(yīng)該有消去 — 更新這樣的畫(huà)面,但是我發(fā)現(xiàn)android是直接把所有東西都計(jì)算了出來(lái),然后再去顯示UI的,而不是邊計(jì)算邊顯示,所以我之前設(shè)置的那些一步步更新畫(huà)面的代碼一點(diǎn)兒用也沒(méi)有,然后我想了想估計(jì)是要用多線程來(lái)寫(xiě),在此之前我沒(méi)有寫(xiě)過(guò)多線程的代碼,所以花了一天時(shí)間看了多線程并把這部分修正了。
大概的想法是這樣的:每次點(diǎn)擊了兩個(gè)相鄰按鈕后,先交換兩個(gè)按鈕,然后調(diào)用線程的start()方法。
在線程重寫(xiě)的run()方法里,先計(jì)算是否存在解(find函數(shù)),如果可以消去,每隔0.03s發(fā)送一個(gè)消息,通知主線程,主線程設(shè)置按鈕的alpha值減?。ㄔ黾油该鞫龋磸?fù)10次,這樣就做出了按鈕慢慢消失的效果。
之后,再停頓0.1s,向主線程發(fā)送一個(gè)消息,主線程開(kāi)始進(jìn)行更新按鈕操作。更新之后,因?yàn)榈袈湎聛?lái)的按鈕還可能組成新的可消去的部分,所以繼續(xù)調(diào)用線程的start方法,直到不存在可消去的按鈕。
如果不存在解,那么,先判斷這是因?yàn)橛脩?hù)點(diǎn)擊的兩個(gè)按鈕無(wú)法產(chǎn)生解,還是之前消去后掉落下來(lái)的按鈕不會(huì)產(chǎn)生解。我們用flag來(lái)記錄這一狀態(tài)。如果是前者,先停頓0.3s,再把兩個(gè)按鈕的位置交換回來(lái);如果是后者,說(shuō)明已經(jīng)消去所有按鈕,這時(shí)我們檢查是否還存在可行解,如果不存在,向主線程發(fā)送一個(gè)消息,通知它更新地圖。
總之就是重寫(xiě)run,以及消息機(jī)制的使用。特別注意的就是run中絕對(duì)不能寫(xiě)UI的操作,UI的事情只能交給主線程做,所以才需要不斷通知。
還有一個(gè)要注意線程之間的執(zhí)行順序,調(diào)試了很久的一個(gè)地方,后來(lái)發(fā)現(xiàn)是因?yàn)橹骶€程那邊還沒(méi)計(jì)算出結(jié)果,但是次線程已經(jīng)開(kāi)始新的計(jì)算了,所以此線程用到的是主線程沒(méi)有更新過(guò)的舊數(shù)據(jù)。后來(lái)想到的方法就是等主線程算完了,再回來(lái)調(diào)用次線程的函數(shù)。
@Override public void run() { if(find()){ flag = false; int n = 10; alpha = 255; while(n--!=0){ wait(30); mHandler.sendEmptyMessage(0); } wait(100); mHandler.sendEmptyMessage(1); } else if(flag==true){ swapMap(p1.x,p1.y,p2.x,p2.y); wait(300); mHandler.sendEmptyMessage(2); } else if(flag==false){ p1 = new point(-2,-2); p2 = new point(-2,-2); if(check()==false){ mHandler.sendEmptyMessage(3); } } }
代碼
main.xml
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="@drawable/background" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/vlayout"> <TextView android:id="@+id/text" android:layout_width="fill_parent" android:layout_height="40dip" android:textSize="18sp" android:autoText="true" android:textColor="#000000" android:capitalize="sentences" android:text="開(kāi)心消消樂(lè)" /> </LinearLayout>
MainActivity.java
package com.example.android.market.licensing; import android.annotation.SuppressLint; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; import android.view.Gravity; import android.view.View; @SuppressLint("NewApi") public class MainActivity extends Activity implements Runnable { private static final String TAG = "App"; private point p1 = new point(-2,-2); private point p2 = new point(-2,-2);; private boolean isPoint1 = true; private int map[][] = new int[8][8]; private int id; private int mark[][] = new int[8][8]; private Thread thread; private int num; private int alpha = 255; boolean flag = false; private int score = 0; private TextView text; ImageButton[] btn = new ImageButton[64]; private int getStyle(){ num = (int)(1+Math.random()*(7-1+1)); switch(num){ case 1:return id = R.drawable.btn1; case 2:return id = R.drawable.btn2; case 3:return id = R.drawable.btn3; case 4:return id = R.drawable.btn4; case 5:return id = R.drawable.btn5; case 6:return id = R.drawable.btn6; case 7:return id = R.drawable.btn7; default:return id = 0; } } public MainActivity() { } private void initBtn(int i,int j) { btn[8*i+j].setBackgroundDrawable (this.getResources().getDrawable(getStyle())); point p = new point(i,j,btn[8*i+j],id); btn[8*i+j].setTag(p); map[i][j] = num; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LinearLayout vlayout = (LinearLayout)findViewById(R.id.vlayout); TableLayout tlayout = new TableLayout(this); TableRow row[] = new TableRow[8]; for(int i=0;i<8;i++){ row[i] = new TableRow(this); row[i].setGravity(Gravity.CENTER); for(int j=0;j<8;j++){ btn[8*i+j] = new ImageButton(this); btn[8*i+j].setLayoutParams(new TableRow.LayoutParams(38,38)); initBtn(i,j); btn[8*i+j].setOnClickListener(listener); row[i].addView(btn[8*i+j]); } tlayout.addView(row[i]); } text = new TextView(this); text.setText("分?jǐn)?shù) : 0"); vlayout.addView(tlayout); vlayout.addView(text); while(find()){ updateState(); } thread = new Thread(this); } private void wait(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { if(find()){ flag = false; int n = 10; alpha = 255; while(n--!=0){ wait(30); mHandler.sendEmptyMessage(0); } wait(100); mHandler.sendEmptyMessage(1); } else if(flag==true){ swapMap(p1.x,p1.y,p2.x,p2.y); wait(300); mHandler.sendEmptyMessage(2); } else if(flag==false){ p1 = new point(-2,-2); p2 = new point(-2,-2); if(check()==false){ mHandler.sendEmptyMessage(3); } } } void swapImage() { p1.v.setBackgroundDrawable (MainActivity.this.getResources().getDrawable(p2.id)); p2.v.setBackgroundDrawable (MainActivity.this.getResources().getDrawable(p1.id)); int tmp =p1.id; p1.id = p2.id; p2.id = tmp; p1.v.setTag(p1); p2.v.setTag(p2); } private void updateBtn(int x1,int y1,int x2,int y2) { map[x1][y1] = map[x2][y2]; point p = (point)(btn[8*x1+y1].getTag()); p.id = ((point)(btn[8*x2+y2].getTag())).id; btn[8*x1+y1].setTag(p); btn[8*x1+y1].setBackgroundDrawable (MainActivity.this.getResources().getDrawable(p.id)); } private void updateBtn(int i,int j) { btn[8*i+j].setBackgroundDrawable (MainActivity.this.getResources().getDrawable(getStyle())); map[i][j] = num; point p = (point)(btn[8*i+j].getTag()); p.id = id; btn[8*i+j].setTag(p); } private void hideBtn(){ alpha -= 25; for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ if(mark[i][j]!=0&&mark[i][j]!=2){ btn[8*i+j].getBackground().setAlpha(alpha); } } } } private void updateState() { for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ btn[8*i+j].getBackground().setAlpha(255); } } for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ if(mark[i][j]==1){ for(int k=i;k>0;k--){ updateBtn(k,j,k-1,j); } updateBtn(0,j); } else if(mark[i][j]>=3){ if(i-mark[i][j]>=0) { updateBtn(i,j,i-mark[i][j],j); updateBtn(i-mark[i][j],j); } else{ updateBtn(i,j); } } else if(mark[i][j]==2){ updateBtn(i,j); } } } } public Handler mHandler=new Handler() { public void handleMessage(Message msg) { switch(msg.what){ case 0:{ hideBtn(); break; } case 1:{ updateState(); text.setText("分?jǐn)?shù) " + ((Integer)score).toString()); thread.start(); break; } case 2:{ swapImage(); p1 = new point(-2,-2); p2 = new point(-2,-2); break; } case 3:{ Toast.makeText(MainActivity.this, "已自動(dòng)生成新地圖", Toast.LENGTH_SHORT).show(); for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ initBtn(i,j); } } while(find()){ updateState(); } break; } } super.handleMessage(msg); } }; private boolean find() { for(int i=0;i<8;i++){ for(int j=0;j<8;j++){ mark[i][j] = 0; } } boolean flag = false; // heng for(int i=0;i<8;i++){ int count = 1; for(int j=0;j<7;j++){ if(map[i][j]==map[i][j+1]){ count++; if(count==3){ flag = true; mark[i][j-1] = 1; mark[i][j] = 1; mark[i][j+1] = 1; score += 15; } else if(count>3){ mark[i][j+1] = 1; score += 5; } } else count = 1; } } //shu for(int j=0;j<8;j++){ int count = 1; for(int i=0;i<7;i++){ if(map[i][j]==map[i+1][j]){ count++; if(count==3){ flag = true; if(mark[i][j]==1)score+=10; else score +=15; mark[i-1][j] = 3; mark[i][j] = 3; mark[i+1][j] = 3; for(int k=i-5;k>=0;k--){ mark[k][j] = 2; } } else if(count>3){ mark[i+1][j] = count; for(int k=1;k<count;k++){ mark[i-k+1][j]++; } if(i-2*count+2>=0)mark[i-2*count+2][j] = 0; score += 5; } } else count = 1; } } return flag; } private boolean check(int i,int j) { //shu int count = 1; if(i>=1&&map[i-1][j]==map[i][j]){ count++; if(i>=2&&map[i-2][j]==map[i][j]){ count++; } } if(count>=3)return true; if(i+1<8&&map[i+1][j]==map[i][j]){ count++; if(i+2<8&&map[i+2][j]==map[i][j]){ count++; } } if(count>=3)return true; //heng count = 1; if(j>=1&&map[i][j-1]==map[i][j]){ count++; if(j>=2&&map[i][j-2]==map[i][j]){ count++; } } if(count>=3)return true; if(j+1<8&&map[i][j+1]==map[i][j]){ count++; if(j+2<8&&map[i][j+2]==map[i][j]){ count++; } } if(count>=3)return true; return false; } private void swapMap(int x1,int y1,int x2,int y2) { int tmp = map[x1][y1]; map[x1][y1] = map[x2][y2]; map[x2][y2] = tmp; } private boolean check() { //heng for(int i=0;i<8;i++){ for(int j=0;j<7;j++){ swapMap(i,j,i,j+1); if(check(i,j)){ swapMap(i,j,i,j+1); return true; } if(check(i,j+1)){ swapMap(i,j,i,j+1); return true; } swapMap(i,j,i,j+1); } } //shu for(int j=0;j<8;j++){ for(int i=0;i<7;i++){ swapMap(i,j,i+1,j); if(check(i,j)){ swapMap(i,j,i+1,j); return true; } if(check(i+1,j)){ swapMap(i,j,i+1,j); return true; } swapMap(i,j,i+1,j); } } return false; } ImageButton.OnClickListener listener = new ImageButton.OnClickListener(){//創(chuàng)建監(jiān)聽(tīng)對(duì)象 @SuppressLint("NewApi") public void onClick(View v) { if(isPoint1){ p1 = (point)v.getTag(); isPoint1 = false; } else { p2 = (point)v.getTag(); isPoint1 = true; } if(((p1.x-p2.x==1||p1.x-p2.x==-1)&&p1.y==p2.y)|| (p1.y-p2.y==1||p1.y-p2.y==-1)&&p1.x==p2.x){ flag = true; swapMap(p1.x,p1.y,p2.x,p2.y); swapImage(); thread.start(); } } }; }
point.java
package com.example.android.market.licensing; import android.view.View; public class point { public int x; public int y; View v; int id; point(int _x,int _y,View _v,int _id){ x = _x; y = _y; v = _v; id = _id; } point(int _x,int _y){ x = _x; y = _y; } }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2010 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.market.licensing" android:versionCode="2" android:versionName="1.1"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <!-- Devices >= 3 have version of Android Market that supports licensing. --> <uses-sdk android:minSdkVersion="3" /> <!-- Required permission to check licensing. --> <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> </manifest>
以上所述是小編給大家介紹的Android開(kāi)心消消樂(lè)代碼實(shí)例詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Android仿開(kāi)心消消樂(lè)大樹(shù)星星無(wú)限循環(huán)效果
- Android游戲源碼分享之2048
- Unity3D游戲引擎實(shí)現(xiàn)在Android中打開(kāi)WebView的實(shí)例
- Android游戲開(kāi)發(fā)實(shí)踐之人物移動(dòng)地圖的平滑滾動(dòng)處理
- Android 游戲開(kāi)發(fā)之Canvas畫(huà)布的介紹及方法
- Android游戲開(kāi)發(fā)之碰撞檢測(cè)(矩形碰撞、圓形碰撞、像素碰撞)
- Android五子棋游戲程序完整實(shí)例分析
- 以一個(gè)著色游戲展開(kāi)講解Android中區(qū)域圖像填色的方法
- Android高仿2048小游戲?qū)崿F(xiàn)代碼
- Android 2d游戲開(kāi)發(fā)之貪吃蛇基于surfaceview
相關(guān)文章
Android使用TextInputLayout創(chuàng)建登陸頁(yè)面
這篇文章主要為大家詳細(xì)介紹了Android使用TextInputLayout創(chuàng)建登陸頁(yè)面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android基于Sensor感應(yīng)器獲取重力感應(yīng)加速度的方法
這篇文章主要介紹了Android基于Sensor感應(yīng)器獲取重力感應(yīng)加速度的方法,涉及Android使用Sensor類(lèi)實(shí)現(xiàn)感應(yīng)重力變化的功能,需要的朋友可以參考下2015-12-12Flutter通過(guò)Container實(shí)現(xiàn)時(shí)間軸效果
時(shí)間軸是前端UI經(jīng)常用到的效果,本文講解下Flutter如何通過(guò)Container實(shí)現(xiàn),感興趣的朋友可以了解下2021-05-05Android6.0 Launcher2應(yīng)用解析
這篇文章主要為大家詳細(xì)介紹了Android6.0 Launcher2應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android自定義流式布局的實(shí)現(xiàn)示例
這篇文章主要介紹了Android自定義流式布局的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Flutter實(shí)現(xiàn)底部導(dǎo)航欄
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)底部導(dǎo)航欄的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02Android自定義SurfaceView實(shí)現(xiàn)畫(huà)板功能
這篇文章主要為大家詳細(xì)介紹了Android自定義SurfaceView實(shí)現(xiàn)畫(huà)板功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07Android SharedPreferences四種操作模式使用詳解
這篇文章主要介紹了Android SharedPreferences四種操作模式使用詳解的相關(guān)資料,這里介紹了獲取Android SharedPreferences的兩種方法及比較,和操作模式的介紹,需要的朋友可以參考下2017-07-07