Android 基于Socket的聊天室實例
Socket是TCP/IP協(xié)議上的一種通信,在通信的兩端各建立一個Socket,從而在通信的兩端之間形成網(wǎng)絡虛擬鏈路。一旦建立了虛擬的網(wǎng)絡鏈路,兩端的程序就可以通過虛擬鏈路進行通信。
Client A 發(fā)信息給 Client B , A的信息首先發(fā)送信息到服務器Server ,Server接受到信息后再把A的信息廣播發(fā)送給所有的Clients
首先我們要在服務器建立一個ServerSocket ,ServerSocket對象用于監(jiān)聽來自客戶端的Socket連接,如果沒有連接,它將一直處于等待狀態(tài)。
Socket accept():如果接收到一個客戶端Socket的連接請求,該方法將返回一個與客戶端Socket對應的Socket
Server示例:
//創(chuàng)建一個ServerSocket,用于監(jiān)聽客戶端Socket的連接請求
ServerSocket ss = new ServerSocket(30000);
//采用循環(huán)不斷接受來自客戶端的請求
while (true){
//每當接受到客戶端Socket的請求,服務器端也對應產生一個Socket
Socket s = ss.accept();
//下面就可以使用Socket進行通信了
...
}
客戶端通??墒褂肧ocket的構造器來連接到指定服務器
Client示例:
//創(chuàng)建連接到服務器、30000端口的Socket
Socket s = new Socket("192.168.2.214" , 30000);
//下面就可以使用Socket進行通信了
...
這樣Server和Client就可以進行一個簡單的通信了
當然,我們要做的是多客戶,所以每當客戶端Socket連接到該ServerSocket之后,程序將對應Socket加入clients集合中保存,并為該Socket啟動一條線程,該線程負責處理該Socket所有的通信任務
//定義保存所有Socket的ArrayList public static ArrayList<Socket> clients = new ArrayList<Socket>();
當服務器線程讀到客戶端數(shù)據(jù)之后,程序遍歷clients集合,并將該數(shù)據(jù)向clients集合中的每個Socket發(fā)送一次。這樣就可以實現(xiàn)一個聊天室的功能了
下面來看看整個功能的demo
先建立一個Java工程,把Server.java運行起來,然后再運行手機模擬器

服務器打印信息:

程序文件結構:

1.先看看主Activity : SocketmsgActivity.java
public class SocketmsgActivity extends Activity {
/** Called when the activity is first created. */
private SQLiteDatabase db;
Thread thread = null;
Socket s = null;
private InetSocketAddress isa = null;
DataInputStream dis = null;
DataOutputStream dos = null;
private String reMsg=null;
private Boolean isContect = false;
private EditText chattxt;
private EditText chatbox;
private Button chatok;
private String chatKey="SLEEKNETGEOCK4stsjeS";
private String name=null,ip=null,port=null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
chattxt = (EditText)findViewById(R.id.chattxt);
chatbox = (EditText)findViewById(R.id.chatbox);
chatok = (Button)findViewById(R.id.chatOk);
chatbox.setCursorVisible(false);
chatbox.setFocusable(false);
chatbox.setFocusableInTouchMode(false);
chatbox.setGravity(2);
//初始化,創(chuàng)建數(shù)據(jù)庫來儲存用戶信息
InitDatabase();
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
try {
Cursor cursor = db.query("config", new String[]{"ip","name","port"},null,null, null, null, null);
while(cursor.moveToNext()){
name = cursor.getString(cursor.getColumnIndex("name"));
ip = cursor.getString(cursor.getColumnIndex("ip"));
port = cursor.getString(cursor.getColumnIndex("port"));
}
cursor.close();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.toString());
}
db.close();
//設置連接
if(ip==null || port==null){
Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
}
//設置名稱
else if(name==null){
Intent intent = new Intent(SocketmsgActivity.this,IniuserActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
}else{
connect();
chatok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String str = chattxt.getText().toString().trim();
System.out.println(s);
try {
dos.writeUTF(chatKey+"name:"+name+"end;"+str);
chattxt.setText("");
}catch (SocketTimeoutException e) {
System.out.println("連接超時,服務器未開啟或IP錯誤");
Toast.makeText(SocketmsgActivity.this, "連接超時,服務器未開啟或IP錯誤", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("連接超時,服務器未開啟或IP錯誤");
Toast.makeText(SocketmsgActivity.this, "連接超時,服務器未開啟或IP錯誤", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
e.printStackTrace();
}
}
});
}
}
private Runnable doThread = new Runnable() {
public void run() {
System.out.println("running!");
ReceiveMsg();
}
};
public void connect() {
try {
s = new Socket();
isa = new InetSocketAddress(ip,Integer.parseInt(port));
s.connect(isa,5000);
if(s.isConnected()){
dos = new DataOutputStream (s.getOutputStream());
dis = new DataInputStream (s.getInputStream());
dos.writeUTF(chatKey+"online:"+name);
/**
* 這里是關鍵,我在此耗時8h+
* 原因是 子線程不能直接更新UI
* 為此,我們需要通過Handler物件,通知主線程Ui Thread來更新界面。
*
*/
thread = new Thread(null, doThread, "Message");
thread.start();
System.out.println("connect");
isContect=true;
}
}catch (UnknownHostException e) {
System.out.println("連接失敗");
Toast.makeText(SocketmsgActivity.this, "連接失敗", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
e.printStackTrace();
}catch (SocketTimeoutException e) {
System.out.println("連接超時,服務器未開啟或IP錯誤");
Toast.makeText(SocketmsgActivity.this, "連接超時,服務器未開啟或IP錯誤", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
e.printStackTrace();
}catch (IOException e) {
System.out.println("連接失敗");
e.printStackTrace();
}
}
public void disConnect() {
if(dos!=null){
try {
dos.writeUTF(chatKey+"offline:"+name);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 線程監(jiān)視Server信息
*/
private void ReceiveMsg() {
if (isContect) {
try {
while ((reMsg = dis.readUTF()) != null) {
System.out.println(reMsg);
if (reMsg != null) {
try {
Message msgMessage = new Message();
msgMessage.what = 0x1981;
handler.sendMessage(msgMessage);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} catch (SocketException e) {
// TODO: handle exception
System.out.println("exit!");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 通過handler更新UI
*/
Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0x1981:
chatbox.setText(chatbox.getText() + reMsg + '\n');
chatbox.setSelection(chatbox.length());
break;
}
}
};
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
disConnect();
//System.exit(0);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
menu.add(0, 1, 1, "初始化設置");
menu.add(0, 2, 2, "退出");
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
if(item.getItemId()==1){
Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);
startActivity(intent);
SocketmsgActivity.this.finish();
}else if(item.getItemId()==2){
disConnect();
SocketmsgActivity.this.finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
return super.onOptionsItemSelected(item);
}
public void InitDatabase(){
if(!config.path.exists()){
config.path.mkdirs();
Log.i("LogDemo", "mkdir");
}
if(!config.f.exists()){
try{
config.f.createNewFile();
Log.i("LogDemo", "create a new database file");
}catch(IOException e){
Log.i("LogDemo",e.toString());
}
}
try {
if(tabIsExist("config")==false){
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
db.execSQL("create table config(_id integer primary key autoincrement," +
"ip varchar(128),port varchar(10),name varchar(32))");
Log.i("LogDemo", "create a database");
db.close();
}
} catch (Exception e) {
// TODO: handle exception
Log.i("LogDemo",e.toString());
}
}
/**
* check the database is already exist
* @param tabName
* @return
*/
public boolean tabIsExist(String tabName){
boolean result = false;
if(tabName == null){
return false;
}
Cursor cursor = null;
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
try {
String sql = "select count(*) as c from sqlite_master where type ='table' " +
"and name ='"+tabName.trim()+"' ";
cursor = db.rawQuery(sql, null);
if(cursor.moveToNext()){
int count = cursor.getInt(0);
if(count>0){
result = true;
}
}
} catch (Exception e) {
// TODO: handle exception
}
cursor.close();
db.close();
return result;
}
}
2.初始化IP和端口Activity, IniActivity.java
public class IniActivity extends Activity{
private EditText ip,port;
private Button nextButton;
private String getip,getport;
private ProgressDialog progressDialog;
private InetSocketAddress isa = null;
private SQLiteDatabase db;
private String ipstring=null,portString=null;
private int row=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.config);
ip = (EditText)findViewById(R.id.ip);
port = (EditText)findViewById(R.id.port);
nextButton = (Button)findViewById(R.id.next);
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
try {
Cursor cursor = db.query("config", new String[]{"ip","port"},null,null, null, null, null);
while(cursor.moveToNext()){
ipstring = cursor.getString(cursor.getColumnIndex("ip"));
portString = cursor.getString(cursor.getColumnIndex("port"));
row++;
}
ip.setText(ipstring);
port.setText(portString);
cursor.close();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.toString());
}
db.close();
nextButton.setOnClickListener(new nextButtonListenner());
}
class nextButtonListenner implements OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
getip = ip.getText().toString().trim();
getport = port.getText().toString().trim();
if(getip=="" || getip==null || getip.equals("")){
Toast.makeText(IniActivity.this, "請輸入IP", Toast.LENGTH_SHORT).show();
ip.setFocusable(true);
}else if(getport=="" || getport==null || getport.equals("")){
Toast.makeText(IniActivity.this, "請輸入端口", Toast.LENGTH_SHORT).show();
port.setFocusable(true);
}else{
//progressDialog = ProgressDialog.show(IniActivity.this, "", "請稍後...", true, false);
//new Thread() {
//@Override
//public void run() {
try {
Socket s = new Socket();
isa = new InetSocketAddress(getip,Integer.parseInt(getport));
s.connect(isa,5000);
//showDialog("連接成功",IniActivity.this);
try {
//生成ContentValues對象
ContentValues values = new ContentValues();
//想該對象當中插入鍵值對,其中鍵是列名,值是希望插入到這一列的值,值必須和數(shù)據(jù)庫當中的數(shù)據(jù)類型一致
values.put("ip", getip);
values.put("port",getport);
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
if(row==0){
db.insert("config", null, values);
}else{
db.update("config", values ,null,null);
}
Toast.makeText(IniActivity.this, "連接成功", Toast.LENGTH_SHORT);
s.close();
Intent intent = new Intent(IniActivity.this,IniuserActivity.class);
startActivity(intent);
IniActivity.this.finish();
db.close();
} catch (Exception e) {
// TODO: handle exception
showDialog("設置失敗,數(shù)據(jù)庫不可用",IniActivity.this);
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
showDialog("連接失敗,IP或者端口不可用",IniActivity.this);
}catch (SocketTimeoutException e) {
System.out.println("連接超時,服務器未開啟或IP錯誤");
showDialog("連接超時,服務器未開啟或IP錯誤",IniActivity.this);
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
showDialog("連接失敗,IP或者端口不可用",IniActivity.this);
}
//progressDialog.dismiss();
//finish();
//}
//}.start();
}
}
}
/**
* define a dialog for show the message
* @param mess
* @param activity
*/
public void showDialog(String mess,Activity activity){
new AlertDialog.Builder(activity).setTitle("信息")
.setMessage(mess)
.setNegativeButton("確定",new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
}
})
.show();
}
}
3.初始化用戶名稱Activity, IniuserActivity.java
public class IniuserActivity extends Activity{
private EditText name;
private Button ok;
private SQLiteDatabase db;
private String nameString;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.configuser);
name = (EditText)findViewById(R.id.name);
ok = (Button)findViewById(R.id.ok);
ok.setOnClickListener(new okButtonListenner());
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
try {
Cursor cursor = db.query("config", new String[]{"name"},null,null, null, null, null);
while(cursor.moveToNext()){
nameString = cursor.getString(cursor.getColumnIndex("name"));
}
name.setText(nameString);
cursor.close();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.toString());
}
db.close();
}
class okButtonListenner implements OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String getname = name.getText().toString().trim();
if(getname==""){
Toast.makeText(IniuserActivity.this, "請輸入您的稱呢", Toast.LENGTH_SHORT).show();
name.setFocusable(true);
}else{
try {
//生成ContentValues對象
ContentValues values = new ContentValues();
//想該對象當中插入鍵值對,其中鍵是列名,值是希望插入到這一列的值,值必須和數(shù)據(jù)庫當中的數(shù)據(jù)類型一致
values.put("name", getname);
db = SQLiteDatabase.openOrCreateDatabase(config.f, null);
db.update("config",values,null,null);
Toast.makeText(IniuserActivity.this, "設置完成", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(IniuserActivity.this,SocketmsgActivity.class);
startActivity(intent);
IniuserActivity.this.finish();
db.close();
} catch (Exception e) {
// TODO: handle exception
showDialog("設置失敗,數(shù)據(jù)庫不可用",IniuserActivity.this);
}
}
}
}
/**
* define a dialog for show the message
* @param mess
* @param activity
*/
public void showDialog(String mess,Activity activity){
new AlertDialog.Builder(activity).setTitle("信息")
.setMessage(mess)
.setNegativeButton("確定",new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
}
})
.show();
}
}
4.config.java
public class config{
public static String SDCARD = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
public static File path = new File(SDCARD+"/RunChatDatabase/"); //數(shù)據(jù)庫文件目錄
public static File f = new File(SDCARD+"/RunChatDatabase/config.db"); //數(shù)據(jù)庫文件
}
布局文件:
1.main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:id="@+id/chatbox" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_weight="1">
</EditText>
<EditText android:id="@+id/chattxt" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:gravity="top"
android:hint="你想和對方說點什么?">
</EditText>
<Button android:id="@+id/chatOk" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Send"
android:textSize="@dimen/btn1">
</Button>
</LinearLayout>
2.config.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">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="初始化設置"
android:textSize="@dimen/h2"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="服務器IP"
android:textSize="@dimen/h3"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="192.168.2.214"
android:id="@+id/ip"
android:textSize="@dimen/et1"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="端口"
android:textSize="@dimen/h3"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="8888"
android:id="@+id/port"
android:textSize="@dimen/et1"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="下一步"
android:id="@+id/next"
android:textSize="@dimen/btn1"/>
</LinearLayout>
3.configuer.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">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="初始化設置"
android:textSize="@dimen/h2"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="您的稱呢"
android:textSize="@dimen/h3"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="潤仔"
android:id="@+id/name"
android:maxLength="20"
android:textSize="@dimen/et1"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="完成"
android:id="@+id/ok"
android:textSize="@dimen/btn1"/>
</LinearLayout>
style文件:dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="h3">30dip</dimen>
<dimen name="h2">40dip</dimen>
<dimen name="btn1">30dip</dimen>
<dimen name="et1">25dip</dimen>
</resources>
最后是服務器文件:Server.java
import java.io.*;
import java.net.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.sound.sampled.Port;
import javax.swing.JOptionPane;
public class Server {
ServerSocket ss = null;
private String getnameString=null;
boolean started = false;
List<Client> clients = new ArrayList<Client>();
List<Info> infos = new ArrayList<Info>();
public static void main(String[] args) {
String inputport = JOptionPane.showInputDialog("請輸入該服務器使用的端口:");
int port = Integer.parseInt(inputport);
new Server().start(port);
}
public void start(int port) {
try {
ss = new ServerSocket(port);
System.out.println("服務器啟動");
started = true;
} catch (BindException e) {
System.out.println(" 端口已經(jīng)被占用");
System.exit(0);
}
catch (IOException e) {
e.printStackTrace();
}
try {
while (started) {
Socket s = ss.accept();
Client c = new Client (s);
System.out.println("a client is connected");
new Thread(c).start();
clients.add(c);
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public List<Client> getClient(){
return clients;
}
class Client implements Runnable {
private String chatKey="SLEEKNETGEOCK4stsjeS";
private Socket s = null;
private DataInputStream dis = null;
private DataOutputStream dos = null;
private boolean bConnected = false;
private String sendmsg=null;
Client (Socket s) {
this.s = s;
try {
dis = new DataInputStream (s.getInputStream());
dos = new DataOutputStream (s.getOutputStream());
bConnected = true;
} catch(IOException e) {
e.printStackTrace();
}
}
public void send (String str) {
try {
//System.out.println(s);
dos.writeUTF(str+"");
dos.flush();
} catch(IOException e) {
clients.remove(this);
System.out.println("對方已經(jīng)退出了");
}
}
public void run() {
try {
while (bConnected) {
String str = dis.readUTF();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String date = " ["+df.format(new Date())+"]";
if(str.startsWith(chatKey+"online:")){
Info info = new Info();
getnameString = str.substring(27);
info.setName(getnameString);
infos.add(info);
for (int i=0; i<clients.size(); i++) {
Client c = clients.get(i);
c.send(getnameString+" on line."+date);
}
System.out.println(getnameString+" on line."+date);
}else if(str.startsWith(chatKey+"offline:")){
getnameString = str.substring(28);
clients.remove(this);
for (int i=0; i<clients.size(); i++) {
Client c = clients.get(i);
c.send(getnameString+" off line."+date);
}
System.out.println(getnameString+" off line."+date);
}
else{
int charend = str.indexOf("end;");
String chatString = str.substring(charend+4);
String chatName = str.substring(25, charend);
sendmsg=chatName+date+"\n"+chatString;
for (int i=0; i<clients.size(); i++) {
Client c = clients.get(i);
c.send(sendmsg);
}
System.out.println(sendmsg);
}
}
} catch (SocketException e) {
System.out.println("client is closed!");
clients.remove(this);
} catch (EOFException e) {
System.out.println("client is closed!");
clients.remove(this);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if (dis != null) dis.close();
if (dos != null) dos.close();
if (s != null) s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class Info{
private String info_name = null;
public Info(){
}
public void setName(String name){
info_name = name;
}
public String getName(){
return info_name;
}
}
}
以上只是一個粗略的聊天室功能,如果要實現(xiàn)私聊,還需要保存該Socket關聯(lián)的客戶信息。一個客戶端可以將信息發(fā)送另一個指定客戶端。實際上,我們知道所有客戶端只與服務器連接,客戶端之間并沒有互相連接。這個功能等我以后有時間再寫個demo.....
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Android Socket實現(xiàn)多個客戶端聊天布局
- android使用Socket通信實現(xiàn)多人聊天應用
- Android Socket通信實現(xiàn)簡單聊天室
- Android使用Websocket實現(xiàn)聊天室
- 基于Socket.IO實現(xiàn)Android聊天功能代碼示例
- android Socket實現(xiàn)簡單聊天小程序
- android socket聊天室功能實現(xiàn)
- android Socket實現(xiàn)簡單聊天功能以及文件傳輸
- Android基于socket實現(xiàn)的簡單C/S聊天通信功能
- Android Socket實現(xiàn)多個客戶端即時通信聊天
相關文章
Android仿淘寶view滑動至屏幕頂部會一直停留在頂部的位置
這篇文章主要介紹了Android仿淘寶view滑動至屏幕頂部會一直停留在頂部的位置的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11
Android開發(fā)學習筆記之通過API接口將LaTex數(shù)學函數(shù)表達式轉化為圖片形式
這篇文章主要介紹了Android開發(fā)學習筆記之通過API接口將LaTex數(shù)學函數(shù)表達式轉化為圖片形式的相關資料,需要的朋友可以參考下2015-11-11
Flutter 利用CustomScrollView實現(xiàn)滑動效果
我們可以使用ListView將幾個GridView組合在一起實現(xiàn)了不同可滑動組件的粘合,但是這里必須要設置禁止 GridView 的滑動,防止多個滑動組件的沖突。這種方式寫起來不太方便,事實上 Flutter 提供了 CustomScrollView 來粘合多個滑動組件,并且可以實現(xiàn)更有趣的滑動效果。2021-06-06
Android實現(xiàn)讀取相機(相冊)圖片并進行剪裁
在 Android應用中,很多時候我們需要實現(xiàn)上傳圖片,或者直接調用手機上的拍照功能拍照處理然后直接顯示并上傳功能,下面將講述調用相機拍照處理圖片然后顯示和調用手機相冊中的圖片處理然后顯示的功能2015-08-08

