移动编程—安卓开发总结
- 项目结构
- UI界面
- LinearLayout
- RelativeLayout
- 事件处理
- SQLite数据库
- 动态数据渲染
- 页面传值
- 远程数据库连接
- JDBC连接
- HTTP连接
- 异步UI刷新
项目结构
-
AndroidManifest.xml
:注册activity (新建时IDE自动注册) -
java后台代码
MainActivity.java
: 程序入口, 同级目录文件夹下存放其他activity- activity: 控制每个页面
- adaptor:控制向前端数据渲染
- view:控制UI界面元素
- bean:存放数据库中对应实体类
- utils:封装工具类,如用于判断从前端获取输入是否为空等
-
layout
- 包含activity ,控制界面元素及其格式
-
drawable
- 包含shape,背景形状控件
-
mipmap
- 包含图片
-
values
string.xml
需要使用的字符串值,便于统一修改color.xml
需要使用的颜色RGB值,便于直接使用dimension.xml
需要用到的尺寸,便于尺寸适配不同机型
-
gradle
-
build.gradle 配置相关依赖
-
gradle-wrapper.properties 配置gradle版本等信息
-
UI界面
参考博客:安卓UI界面开发经典四大布局,此处只记录(B站教学视频里)最常用的两种布局方式。
LinearLayout
整个Android布局中的控件摆放方式是以线性的方式摆放的,最常用。
排列方式:
- 纵向:
android:orientation="vertical"
- 横向:
android:orientation="horizontal"
系统默认采用横向布局
权重:
类似H5前端开发弹性布局中flex的数值,设置总权重时部分权重超过后的元素将被忽略。
线性布局中可以规定控件的权重,通过android:layout_weight=""
实现。下面我们来看一起权重的经典问题。我们先不设置总权重,设置子元素的宽度为0dp
。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:gravity="center_vertical" //对齐方式
android:orientation="horizontal" //布局方向
>
<!--横向布局-->
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:layout_weight="1"/>
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:layout_weight="2"/>
</LinearLayout>
显示效果如下:
RelativeLayout
通过相对布局,可以实现控件的重叠。当控件大量重叠时,用相对布局更加方便。
它在MarginLayout
的基础上,添加了对齐方法layout_alignBottom="@+id/iv"
。
下面是一些简单的属性:
属性 | 作用 |
---|---|
layout_marginRight | 控件与界面右侧距离 |
layout_toRightOf | 将该控件的右边缘与给定ID的控件左边缘对齐 |
layout_alignRight | 将该控件的右边缘与给定ID的右边缘对齐 |
layout_alignParentRight | 将该控件的右部与其父控件的右部对齐 |
layout_centerInParent | 将该控件的置于父控件的中央 |
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="horizontal"
android:weightSum="3">
<View
android:id="@+id/v1"
android:layout_width="300dp"
android:layout_height="200dp"
android:background="@color/colorPrimaryDark" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent"
android:layout_alignRight="@id/v1"
android:layout_alignBottom="@id/v1"
android:layout_marginRight="50dp"/>
</RelativeLayout>
效果图如下:
事件处理
方法1:通过ID绑定设置事件监听器实现:
在前端指定元素ID:
<Button
android:id="@+id/btn_login"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="@string/register"
android:background="@drawable/login_btn">
</Button>
后台根据ID找到元素:
public class MainActivity extends AppCompatActivity {
private Button btnLogin; //声明变量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//根据ID找到元素
btnLogin = findViewById(R.id.btn_login);
//绑定点击事件
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/*事件处理*/
}
});
}
}
方法2:实现前端onClick点击事件
前端:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="Login"/>
后台:
public void Login(View view) {
/*处理事件*/
}
SQLite数据库
参考教程:B站安卓零基础入门-第85讲
以单例模式创建工具类,需要注意SQLiteDatabase中主键必须是自增_id
public class DBHelper extends SQLiteOpenHelper {
private static SQLiteOpenHelper mInstance;
public static synchronized SQLiteOpenHelper getInstance(Context context){
if (mInstance == null){
//创建数据库
mInstance = new DBHelper(context, "droid.db", null, 1);
}
return mInstance;
}
private DBHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建数据库sql语句
String sql = "CREATE TABLE book(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name TEXT," +
"type TEXT," +
"course TEXT" +
")";
//执行sql语句
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
操作数据库数据:
需要注意execSQL
函数的第二个参数顺序需要和待填写项对应
public class BookDB{
private static BookDB bookDB;
private SQLiteOpenHelper dbHelper;
public static synchronized BookDB getInstance(Context context){
if(bookDB == null){
bookDB = new BookDB(context);
}
return bookDB;
}
private BookDB(Context context){
dbHelper = DBHelper.getInstance(context);
}
//修改数据库
public void insert(Book book){
SQLiteDatabase db = dbHelper.getWritableDatabase();
if(db.isOpen()){
db.execSQL("INSERT INTO book (name, type, course) VALUES (? , ?, ?)",
new Object[]{book.getName(), book.getId(), book.getCourse()});
}
db.close();
}
//查询数据库
public List<Book> listBooks(){
SQLiteDatabase db = dbHelper.getReadableDatabase();
List<Book> list = new ArrayList<>();
if(db.isOpen()){
Cursor cursor = db.rawQuery("select * from book", null);
while(cursor.moveToNext()){
Book book = new Book();
book.setId(cursor.getInt(cursor.getColumnIndex("_id")));
book.setName(cursor.getString(cursor.getColumnIndex("name")));
book.setType(cursor.getString(cursor.getColumnIndex("type")));
book.setCourse(cursor.getString(cursor.getColumnIndex("course")));
list.add(book);
}
cursor.close();
}
db.close();
return list;
}
}
动态数据渲染
参考教程:B站订单APP项目实战-第6集
完成动态数据渲染需要以下准备:
adapter.java
bean.java
activity.java
activity_list.xml
item.xml
下面以显示从本地数据库查询到的多本书籍信息为例子,效果图如下:
1、编写book_item.xml
完成单项布局
2、编写activity_book_list.xml
,创建ListView
视图显示数据项
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
<com.example.myapplication.view.BookListView
android:id="@+id/book_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ScrollView>
此处为了解决ListView
的高度适配问题,新建了视图类BookListView
重写了onMeasure
方法:
public class BookListView extends ListView {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE<<2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightSpec);
}
}
3、编写book.java
存放数据库对应实体类
4、编写BookAdaptor.java
,控制数据向book_item.xml
布局中渲染
public class BookAdapter extends BaseAdapter {
private Context context; //语境
private List<Book> data; //数据集
public BookAdapter(Context context){
data = new LinkedList<>();
this.context = context;
}
//getter and setter of data
public List<Book> getBookList() {
return data;
}
public void setBookList(List<Book> bookList) {
this.data.clear();
this.data.addAll(bookList);
notifyDataSetChanged();
}
//重写父类的各个方法
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return data.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if(convertView == null){
//加载数据单项layout: book_item.xml
convertView = LayoutInflater.from(context).inflate(R.layout.book_item, null);
//根据绑定界面元素
viewHolder = new ViewHolder();
viewHolder.bookName = convertView.findViewById(R.id.book_name);
viewHolder.bookType = convertView.findViewById(R.id.book_type);
viewHolder.course = convertView.findViewById(R.id.course);
viewHolder.editBtn = convertView.findViewById(R.id.edit_btn);
viewHolder.deleteBtn = convertView.findViewById(R.id.delete_btn);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ViewHolder) convertView.getTag();
}
//向前端渲染数据
Book book = data.get(position);
viewHolder.bookName.setText(book.getName());
viewHolder.bookType.setText("类型:"+book.getType());
viewHolder.course.setText("课程:"+book.getCourse());
//绑定点击事件,实现页面传值(代码见相应小节)
return convertView;
}
//ViewHolder类,关联前端界面元素
class ViewHolder{
TextView bookName, bookType, course;
Button editBtn, deleteBtn;
}
}
5、在BookListActivity.java
中,绑定Adaptor
与前端ListView
public class BookListActivity extends BookDB {
private ListView listView;
private BookAdapter adapter;
private BookDB bookDB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_list);
bookDB = bookDB.getInstance(this);
//初始化页面
initView();
}
private void initView() {
//获取数据
List<Book> list = bookDB.listBooks();
adapter = new BookAdapter(this);
adapter.setBookList(list);
//给前端 ListView 绑定 Adaptor
listView = findViewById(R.id.book_list);
listView.setAdapter(adapter);
}
}
页面传值
以上小节中点击书籍单项后跳转到详情为例,跳转时需要携带点击的书籍ID,用于在详情页查询相关信息。
跳转要求:携带实体类Book.java
作为参数,需要该类实现接口implements Serializable
1、传递参数
在BookAdaptor.java
的getView()
方法返回前绑定点击跳转事件,转递点击对象作为参数
//绑定点击事件
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, BookDetailActivity.class);
intent.putExtra("book", book); //传值
context.startActivity(intent); //跳转
}
});
2、接收参数
书籍详情页面BookEditActivity.java
接收参数,并初始化视图
public class BookEditActivity extends BaseActivity {
private Book book;
private EditText nameText;
private EditText typeText;
private EditText courseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_edit);
//得到页面传递的参数
book = (Book) getIntent().getSerializableExtra("book");
System.out.println(book.getName());
//绑定元素
nameText = findViewById(R.id.book_name);
typeText = findViewById(R.id.book_type);
courseText = findViewById(R.id.course);
//初始化页面
nameText.setText(book.getName());
typeText.setText(book.getType());
courseText.setText(book.getCourse());
}
public void editBook(View view) {
editBookInfo();
BookDB bookDB = BookDB.getInstance(this);
bookDB.update(book);
showToast("修改书籍信息成功");
this.finish();
}
private Book editBookInfo() {
String name = nameText.getText().toString().trim();
String type = typeText.getText().toString().trim();
String course = courseText.getText().toString().trim();
if(StringUtils.isValid(name) && StringUtils.isValid(type) && StringUtils.isValid(course)){
book.setName(name);
book.setType(type);
book.setCourse(course);
}
else {
return null;
}
return book;
}
}
远程数据库连接
注意事项:
1、安卓客户端中网络请求均为异步,不可以在主线程中进行。
2、需要安装合适版本驱动包并在build.gradle
中配置
3、需要在AndroidManifest.xml
中允许网络请求
参考教程:CSDNAndroid连接Mysql数据库教程
JDBC连接
public class JDBCHelper{
private final String DRIVER = "com.mysql.jdbc.Driver";
private final String URL = "jdbc:mysql://www.ylxteach.net:3366/ydbc2021";
private final String USER = "******";
private final String PWD = "******";
private static JDBCHelper instance;
public static JDBCHelper getInstance() {
if(instance == null){
instance = new JDBCHelper();
}
return instance;
}
//修改数据库
public void addBook(Book book){
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER, PWD);
PreparedStatement statement = conn.prepareStatement(
"INSERT INTO lmj_book (bname, type, course) VALUES (? , ?, ?)");
statement.setString(1, book.getName());
statement.setString(2, book.getType());
statement.setString(3, book.getCourse());
statement.executeUpdate();
statement.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
HTTP连接
1、在gradle
中导入依赖
okhttp3
用于发送客户端请求,gson
用于解析服务器返回消息。
implementation 'com.google.code.gson:gson:2.6.2'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
2、解析服务器返回消息
可以看到数据库信息在aaData
数组中:
通过定义工具类实现图书信息解析:
需要注意的是Book
中每个成员变量的命名需要与数据库中字段名称对应,否则解析失败。
public class BookJsonParser {
private static BookJsonParser instance;
private BookJsonParser(){
}
public static BookJsonParser getInstance(){
if(instance==null){
return new BookJsonParser();
}
else {
return instance;
}
}
public List<Book> getBook(String content){
//获取返回结果的aaData数组
JsonObject returnObj = new JsonParser().parse(content).getAsJsonObject();
JsonArray data = returnObj.get("aaData").getAsJsonArray();
//解析为书籍列表
return new Gson().fromJson(data, new TypeToken<List<Book>>(){}.getType());
}
}
3、向服务器发送POST请求并获取返回数据
**注意事项:**POST消息请求头注明标蓝字段
public class HttpHelper {
//查
public static void listAllBook(Handler handler) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//发送POST请求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder().build();
Request request = new Request.Builder()
.url(Constants.WEB_SITE+"select * from lmj_book")
.addHeader("X-Requested-With", "XMLHttpRequest")
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request);
Response response = call.execute();
//解析服务器返回json
String res = response.body().string();
//与UI进程通信
handler.obtainMessage(1, BookJsonParser.getInstance().getBook(res)).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
异步UI刷新
推荐使用android.os.Handler
完成线程间通信,解决异步界面刷新问题(注意导包不要出现同名异包错误)。
在数据库操作线程:
//数据查询
public void listAllBook(Handler handler){
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
List<Book> list = new LinkedList<>();
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER, PWD);
Statement statement = (Statement) conn.createStatement();
ResultSet res = statement.executeQuery("SELECT * FROM lmj_book");
while (res.next()){
Book book = new Book();
book.setId(res.getInt("_id"));
book.setName(res.getString("bname"));
book.setType(res.getString("type"));
book.setCourse(res.getString("course"));
list.add(book);
}
statement.close();
conn.close();
//利用handler向UI线程发送查询数据
handler.obtainMessage(1, list).sendToTarget();
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
});
thread.start();
}
在UI线程:
public class BookListActivity extends BaseActivity {
private BookAdapter adapter;
private ListView listView;
private Handler handler;
private JDBCHelper helper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_list);
adapter = new BookAdapter(this);
helper = JDBCHelper.getInstance();
listView = findViewById(R.id.book_list);
listView.setAdapter(adapter);
handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
//handler接收消息并处理
adapter.setBookList((List<Book>) msg.obj);
break;
}
}
};
//初始化页面
initView();
}
private void initView() {
//JDBC获取数据
helper.listAllBook(handler);
}
public void AddBook(View view) {
navigateTo(com.example.myapp.activity.BookAddActivity.class);
}
public void Refresh(View vi在这里插入代码片ew) {
reload();
}
}