03_用户界面

03_用户界面

0. 如何排查解决常见异常

常见的异常:

  1. NullPointerException

    原因:调用对象的方法/属性,但对象为null

  2. ClassCastException

    原因:类型转换异常

  3. ActivityNotFoundException

    原因:没有注册Activity,或注册不正确

基本常见异常的一般分析步骤:

  1. 在logcat中从下向上找,尽量找到causeBy(),会显示哪种异常导致的(AS的problems栏也可)

  2. 找到出异常的类及行号,点击进入对应的行

1. 理论概述

1.1 理解UI

UI的定义

  • user interface,意为:用户界面

  • UI由ViewViewGroup组成

  • View类是所有视图(包括ViewGroup)的根基类

  • View在屏幕上占据一片矩形区域,并会在上面进行内容绘制

  • ViewGroup包含一些View或ViewGroup,用于控制子View的布局

View的hierarchy结构

ViewGroup下重要的子类

 

 

 

UI的组成

  • 界面的整体布局(Layout)

  • 组成可视界面的各个UI组件(Component)

     

     

     

1.2 UI事件

理解UI事件

  • 当用户通过手指触摸UI时,系统会自动创建对象的Event对象

  • Android中提供了多种方式拦截处理不同类型的事件

  • 视图本身就可以处理发生在该视图上的事件

  • 事件是什么

    点击、长按、触碰...

  • 谁是事件源

    发生事件的源头,视图

  • 谁是事件监听器

    new Listener

使用UI事件

  • Android提供了很多不同类型的事件监听器接口

    • View.OnClickListener:onClick()

    • View.OnLongClickListener:onLongClick()

    • View.OnTouchListener:onTouch()

    • View.OnCreateContextMenuListener:onCeateContextMenu()

    • View.OnFocusChangeListener:onFocusChange()

    • View.OnKeyListener:onKey()

  • 给视图添加事件监听的方式

    view.setOn...Listener(listener)

    • new

    • this

    • 成员变量

    • 布局绑定

2. UI开发

2.1 常用UI组件

TextView EditText Button ImageView CheckBox RadioGroup RadioButton Toast

OptionMenu ContextMenu

ProgressBar SeekBar

Dialog

测试用例

 

 

 

 @Override
 public void onClick(View v) {
  switch (v.getId()) {
  case R.id.simple_btn:
  startActivity(new Intent(this, SimpleActivity.class));
  break;
  case R.id.menu_btn:
  startActivity(new Intent(this, MenuActivity.class));
  break;
  case R.id.progressbar_btn:
  startActivity(new Intent(this, ProgressActivity.class));
  break;
  case R.id.dialog_btn:
  startActivity(new Intent(this, DialogActivity.class));
  break;
  default:
  break;
  }
 }

简单组件

测试常用简单的Component

 

 

 

TextView:文本框
 <TextView
  android:text="这是TextView的内容"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:id="@+id/simple_tv"
  android:background="#e6e6e6"
  android:textColor="#90a"
  android:textSize="20sp" />
属性含义
text 文本内容
textColor 字体颜色
textStyle 字体风格 normal / bold / italic
textSize 字体大小,单位sp
background 背景颜色
gravity 对齐方向
 private TextView simple_tv;
 iv_simple = findViewById(R.id.iv_simple);
 simple_tv.getText(); // 获取文本
 simple_tv.setText("被改变了~~"); // 设置文本
EditText:输入框
 <EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:inputType="phone"
  android:id="@+id/et_phone"
  android:hint="请输入手机号" />
属性含义
text 文本
hint 提示默认文本
textColorHint 提示文本颜色
selectAllOnFocus 获得焦点后全选文本 true / false(默认)
inputType 输入类型
minLines / maxLines 设置最小行,最大行 number
singleLine 单行输入 true (默认多行显示,自动换行)
textScaleX / textScaleY 设置文件水平间隔、垂直间隔
capitalize 英文字母大写类型 none(默认) / sentences(第一个字母大写) / characters(全部大写)
 String number = et_phone.getText().toString();
 et_phone.setText("110");
Button: 按钮
 <Button
  android:text="提交"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:textAllCaps="false" // 取消全部大写(默认开启)
  android:id="@+id/btn_submit" />
 btn_submit.setOnClickListener(v -> {
  String number = et_phone.getText().toString();
  Toast.makeText(SimpleActivity.this, number, Toast.LENGTH_SHORT).show();
 });
ImageView:图片视图
 <ImageView
  android:id="@+id/iv_simple"
  android:layout_width="70dp"
  android:layout_height="70dp"
  android:backgroud="@drawable/ic_launcher" // 背景图片
  android:src="@android:drawable/ic_media_play" /> // 前景图片,android:drawable系统图片
 iv_simple.setOnClickListener(v -> {
  // 设置背景图片
  iv_simple.setBackgroundResource(android.R.drawable.alert_light_frame);
  // 设置前景图片
  iv_simple.setImageResource(android.R.drawable.ic_media_pause);
 });
CheckBox:多选框
 <CheckBox
  android:text="篮球"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:id="@+id/checkBox"
  android:checked="true" />
 boolean isChecked() // 判断是否被勾选
 void setChecked(boolean checked) // 设置勾选
 void setOnCheckedChangeListener(OnCheckedChangeListener listener) // 状态改变监听
 
 cb_pingpong.setOnCheckedChangeListener((buttonView, isChecked) -> {
  if(isChecked) {
  //Log.e("TAG",this.toString()); // E/TAG: com.example.helloandroid.SimpleActivity@19a89e77
  // 编译为匿名内部方法,this 指针指向lambda 外部类
  Toast.makeText(this, "选中了乒乓球", Toast.LENGTH_SHORT).show();
  }
 });
RadioGroup:单选框组 和 RadioButton:单选
 <RadioGroup
  android:layout_width="409dp"
  android:layout_height="45dp"
  android:orientation="horizontal"
  android:id="@+id/rg_gender"
  android:layout_marginLeft="5dp">
 
  <RadioButton
  android:text="男"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:id="@+id/rb_male" />
 
  <RadioButton
  android:text="女"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:checked="true"
  android:id="@+id/rb_female" />
 
  <RadioButton
  android:text="无性别"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:id="@+id/rb_no_gender" />
 </RadioGroup>
 rg_gender.setOnCheckedChangeListener((group, checkedId) -> { // checkedId 选中的 radioButton 的 id
  RadioButton rb = findViewById(checkedId);
  String val = rb.getText().toString();
  Toast.makeText(this, val, Toast.LENGTH_SHORT).show();
 });

菜单组件

测试菜单Component

 

 

 

文字换行可以直接在文本中添加\n

关于Menu的三个问题
  1. 如何触发Menu的显示?

  2. 如何向Menu中添加MenuItem?

  3. 选择某个MenuItem时如何响应?

OptionMenu:选项菜单
  • OptionMenu在点击手机的menu键触发(一般为应用右上角三个点)

  • Activity:onCreateOptionsMenu(Menu menu)

    • 显示OptionMenu的回调方法,在此方法中向Menu中添加MenuItem

  • 加载menuItem的两种方式:

    • 纯编码方式:menu.add(...)

    • 加载menu文件的方式:

      • MenuInflater menuInflater = getMenuInflater();

      • menuInflater.inflate(R.menu.main_option, menu);

  • Activity:onOptionsItemSelected(Menu menu)

    • 当选择某个菜单项的回调方法

1. 纯编码方式

 // 用来显示menu的方法:向menu中添加item
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // 纯编码方式
  menu.add(0, 2, 0, "添加");
  menu.add(0, 3, 0, "删除");
  return super.onCreateOptionsMenu(menu);
 }
 // 选择菜单项回调方法
 @Override
 public boolean onOptionsItemSelected(@NonNull MenuItem item) {
  switch (item.getItemId()) {
  case 2:
  Toast.makeText(this, "添加", Toast.LENGTH_SHORT).show();
  break;
  case 3:
  Toast.makeText(this, "删除", Toast.LENGTH_SHORT).show();
  break;
  default:
  break;
  }
  return super.onOptionsItemSelected(item);
 }

2. 加载menu文件方式

res文件夹右键新建menu的xml文件

 

 

 

type选择menu

 

 

 

添加item

 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item
  android:id="@+id/add"
  android:title="添加2" />
  <item
  android:id="@+id/delete"
  android:title="删除2" />
 </menu>

Java文件

  @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // 加载菜单文件方式
  // 1) 得到菜单加载器
  MenuInflater menuInflater = getMenuInflater();
  // 2) 加载菜单文件
  menuInflater.inflate(R.menu.option_menu, menu);
  return super.onCreateOptionsMenu(menu);
 }
 
 @Override
 public boolean onOptionsItemSelected(@NonNull MenuItem item) {
  switch (item.getItemId()) {
  case R.id.add: // 注意 id
  Toast.makeText(this, "添加", Toast.LENGTH_SHORT).show();
  break;
  case R.id.delete:
  Toast.makeText(this, "删除", Toast.LENGTH_SHORT).show();
  break;
  default:
  break;
  }
  return super.onOptionsItemSelected(item);
 }

 

 

 

ContextMenu:上下文菜单
  • view:setOnCreateContextMenuListener(listener)

    • 为某个视图添加创建ContextMenu的监听(需要长按触发)

  • view:onCreateContextMenu(menu, view, menuInfo)

    • 显示菜单的回调方法

  • Activity:onContextItemSelected(Menuitem item)

    • 当选择某个菜单项的回调方法

  1. 如何触发Menu的显示?

    长按某个视图 view.setOnCreateContextMenuListener(listener)

  2. 如何向Menu中添加MenuItem?

    重写 onCreateContextMenu(menu, view, menuInfo)

    menu.add() / 加载menu文件

  3. 选择某个MenuItem时如何响应?

    onContextItemSelected(Menuitem item)

代码实现

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_menu);
 
  btn_context = findViewById(R.id.btn_context);
  // class Activity implements OnCreateContextMenuListener 直接写this就行
  btn_context.setOnCreateContextMenuListener(this);
 }
 
 @Override
 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
  super.onCreateContextMenu(menu, v, menuInfo);
 
  menu.add(0, 1, 0, "添加");
  menu.add(0, 4, 0, "删除");
 }
 
 @Override
 public boolean onContextItemSelected(@NonNull MenuItem item) {
  switch (item.getItemId()) {
  case 1:
  Toast.makeText(this, "添加", Toast.LENGTH_SHORT).show();
  break;
  case 4:
  Toast.makeText(this, "删除", Toast.LENGTH_SHORT).show();
  break;
  default:
  break;
  }
  return super.onContextItemSelected(item);
 }

 

 

 

进度条组件

测试进度条Component

 

 

 

ProgressBar:进度条
 <ProgressBar
  style="?android:attr/progressBarStyle" // 默认为圆形进度条
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:id="@+id/progressBar" />
 <ProgressBar
  style="?android:attr/progressBarStyleHorizontal" // 水平进度条
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:progress="2" // 当前进度,默认为0
  android:max="10" // 最大进度,默认100
  android:id="@+id/progressBar2" />
  • ProgressBar

    • void setProgress(int progress):设置当前进度

    • int getProgress():获取进度

    • void setMax(int max):设置最大进度

    • int getMax():得到最大进度

  • View

    • void setVisibility(int visibility):设置视图的可见性

    • View.VISIBLE:标识可见

    • View.INVISIBLE:标识不可见,但占屏幕空间

    • View.GONE:标识不可见,也不占屏幕空间

SeekBar:可手动滑动的进度条(是progressBar子类)
 <SeekBar
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:id="@+id/sb" />
  • SeekBar:

    • setOnSeekBarChangeListener(OnSeekBarChangeListener l):设置改变的监听

  • OnSeekBarChangeListener:

    • onProgressChanged(SeekBar seekBar, int progress, boolean fromUser):进度改变

    • onStartTrackingTouch(SeekBar seekBar):按下滑杆

    • onStopTrackingTouch(SeekBar seekbar):从滑杆离开

测试用例实现

布局文件

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/cl_progress_layout"
  tools:context=".ProgressActivity">
 
  <LinearLayout
  android:orientation="horizontal"
  android:layout_width="409dp"
  android:layout_height="56dp"
  android:id="@+id/ll_progressbar_layout"
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  android:layout_marginTop="24dp">
 
  <ProgressBar
  style="?android:attr/progressBarStyle"
  android:layout_width="56dp"
  android:layout_height="match_parent"
  android:layout_marginStart="150dp"
  android:id="@+id/pb_1" />
 
  <TextView
  android:text="正在加载中..."
  android:gravity="center"
  android:layout_width="wrap_content"
  android:layout_height="match_parent"
  android:id="@+id/textView4" />
  </LinearLayout>
 
  <ProgressBar
  style="?android:attr/progressBarStyleHorizontal"
  android:layout_width="379dp"
  android:layout_height="wrap_content"
  android:id="@+id/pb_2"
  app:layout_constraintTop_toBottomOf="@+id/ll_progressbar_layout"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  android:layout_marginTop="24dp" />
 
  <SeekBar
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:id="@+id/sb"
  app:layout_constraintTop_toBottomOf="@+id/pb_2"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  android:layout_marginStart="16dp"
  android:layout_marginEnd="16dp"
  android:layout_marginTop="24dp"
  app:layout_constraintHorizontal_bias="0.0" />
 
  <TextView
  android:text="@string/progress_desc"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:id="@+id/textView2"
  android:textSize="18sp"
  app:layout_constraintTop_toBottomOf="@+id/sb"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  android:layout_marginTop="24dp"
  android:layout_marginStart="8dp"
  android:layout_marginEnd="8dp"
  app:layout_constraintHorizontal_bias="0.0" />
 </androidx.constraintlayout.widget.ConstraintLayout>

代码

 public class ProgressActivity extends AppCompatActivity {
 
  private LinearLayout ll_progress_layout;
  private SeekBar sb;
  private ProgressBar pb_2;
 
  private final SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
  @Override
  public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // 移动滑杆
  }
 
  @Override
  public void onStartTrackingTouch(SeekBar seekBar) { // 按下滑杆
  }
 
  @Override
  public void onStopTrackingTouch(SeekBar seekBar) { // 离开滑杆
  int progress = seekBar.getProgress();
  pb_2.setProgress(progress);
 
  if(progress==seekBar.getMax()) {
  ll_progress_layout.setVisibility(View.GONE);
  }
  }
  };
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_progress);
 
  ll_progress_layout = findViewById(R.id.ll_progressbar_layout);
  sb = findViewById(R.id.sb);
  pb_2 = findViewById(R.id.pb_2);
 
  sb.setOnSeekBarChangeListener(onSeekBarChangeListener);
  }
 }

对话框组件

测试对话框Component

 

 

 

  1. 测试AlertDialog

  2. 测试ProgressDialog

  3. 测试DatePickerDialog TimePickerDialog

Dialog的hierarchy

 

 

 

显示一般AlertDialog

AlertDialog:

  • show():显示警告框

  • 没有公开的构造方法,只能通过其内部类Builder来创建

AlertDialog.Builder:

  • create():创建AlertDialog对象

  • show():创建AlertDialog对象,同时显示出来

  • setTitle(CharSequence title):设置标题

  • setMessage(CharSequence message):设置内容

  • setPositiveButton(String text, OnClickListener l):设置正面按钮

  • setNegativeButton(String text, OnClickListener l):设置负面按钮

  • dismiss():移除dialog

 btn_show_simple_alert.setOnClickListener(v -> {
  new AlertDialog.Builder(this)
  .setTitle("删除数据")
  .setMessage("确定删除吗?")
  .setPositiveButton("删除", new DialogInterface.OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog, int which) {
  Toast.makeText(DialogActivity.this, "删除", Toast.LENGTH_SHORT).show();
  }
  })
  .setNegativeButton("取消", new DialogInterface.OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog, int which) {
  Toast.makeText(DialogActivity.this, "取消", Toast.LENGTH_SHORT).show();
  }
  })
  .show(); // 链式调用
 });

 

 

 

显示单选列表AlertDialog

setSingleChoiceItems(...) 设置单选列表

 btn_show_radio_alert.setOnClickListener(v -> {
  final String[] items = {"红", "蓝", "绿", "灰"};
  new AlertDialog.Builder(this)
  .setTitle("指定背景颜色")
  .setSingleChoiceItems(items, 2, new DialogInterface.OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog, int which) {
  Toast.makeText(DialogActivity.this, items[which], Toast.LENGTH_SHORT).show();
  dialog.dismiss(); // 需要手动关闭 dialog
  }
  })
  .show();
 });

 

 

显示自定义AlertDialog
  • 动态加载布局文件得到对应的View对象

    View.inflate(Context context, int resource, ViewGroup root);

  • 设置View

    DialogBuilder :setView(View view)设置dialog中的视图

新建一个布局文件 res/layout/dialog_view.xml

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">
 
  <ImageView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  app:srcCompat="@drawable/title"
  android:id="@+id/imageView"
  android:scaleType="fitXY"
  android:layout_weight="1" />
 
  <EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:inputType="textPersonName"
  android:ems="10"
  android:hint="UserName"
  android:layout_marginTop="16dp"
  android:layout_marginLeft="4dp"
  android:layout_marginRight="4dp"
  android:layout_marginBottom="4dp"
  android:id="@+id/et_name"
  android:layout_weight="1" />
 
  <EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:inputType="textPassword"
  android:ems="10"
  android:hint="password"
  android:layout_marginTop="4dp"
  android:layout_marginLeft="4dp"
  android:layout_marginRight="4dp"
  android:layout_marginBottom="16dp"
  android:id="@+id/et_pwd"
  android:layout_weight="1" />
 </LinearLayout>
 btn_show_custom_alert.setOnClickListener(v -> {
  // 动态加载布局文件,得到对应的View对象
  View view = View.inflate(this, R.layout.dialog_view, null);
  // View的真实类型? 是布局文件根标签的类型
  // 如何得到一个独立View的子View? view.findViewById(id)
  EditText et_name = view.findViewById(R.id.et_name);
  EditText et_pwd = view.findViewById(R.id.et_pwd);
  new AlertDialog.Builder(this)
  .setView(view)
  .setNegativeButton("取消", null)
  .setPositiveButton("确定", new DialogInterface.OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog, int which) {
  String userName = et_name.getText().toString();
  String pwd = et_pwd.getText().toString();
  Toast.makeText(DialogActivity.this, userName + ": " + pwd, Toast.LENGTH_SHORT).show();
  }
  })
  .show();
 });

 

 

显示圆形进度ProgressDialog

ProgressDialog:

static show(Context context, CharSequence title, CharSequence message):显示dialog

ProgressDialog(Context context):构造方法

setProgressStyle(int style):设置样式

ProgressDialog.STYLE_HORIZONTAL:水平进度条样式

 btn_show_circle_progress.setOnClickListener(v -> { // 回调方法都在主线程执行
  // 1、final 关键字,提高了性能,JVM 和 Java 应用都会缓存 final 变量。
  // 2、final 变量,可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
  final ProgressDialog dialog = ProgressDialog.show(this, "数据加载", "数据加载中...");
  // 模拟一个长时间的工作
  // 长时间的工作不能在主线程做,得启动分线程完成
  new Thread() {
  @Override
  public void run() { // 分线程
  for (int i = 0; i < 20; i++) {
  try {
  Thread.sleep(100);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  dialog.dismiss(); // 方法在分线程执行,但内部使用handler实现主线程移除dialog
  // 不能Toast 不能在分线程直接更新UI
  //Toast.makeText(DialogActivity.this, "加载完成了", Toast.LENGTH_SHORT).show();
  runOnUiThread(new Runnable() {
  @Override
  public void run() { // 在主线程执行
  Toast.makeText(DialogActivity.this, "加载完成了", Toast.LENGTH_SHORT).show();
  }
  });
  // runOnUiThread在分线程执行
  // 用 new Thread(runOnUiThread).start() 则在分线程执行
  }
  }.start();
 });

 

 

显示水平进度ProgressDialog
 btn_show_horizon_progress.setOnClickListener(v -> {
  ProgressDialog pd = new ProgressDialog(this);
  pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
  pd.show();
  new Thread(new Runnable() {
  @Override
  public void run() {
  int count = 20;
  pd.setMax(count);
  for (int i = 0; i < count; i++) {
  try {
  Thread.sleep(100);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  pd.setProgress(pd.getProgress() + 1);
  }
  pd.dismiss();
  }
  }).start();
 });

 

 

显示DatePickerDialog
 btn_show_date_picker.setOnClickListener(v -> {
  // 创建日历对象
  Calendar calendar = Calendar.getInstance();
  // 得到当前的年月日
  int year = calendar.get(Calendar.YEAR);
  int month = calendar.get(Calendar.MONTH);
  int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
  Log.e("TAG", year + "-" + (month + 1) + "-" + dayOfMonth); // 初始日期 month从0开始
  new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
  @Override
  public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
  Log.e("TAG_IN", year + "-" + (month + 1) + "-" + dayOfMonth); // 设置后的日期
  }
  }, year, month, dayOfMonth).show();
 });
显示TimePickerDialog
 btn_show_time_picker.setOnClickListener(v -> {
  // 创建日历对象
  Calendar calendar = Calendar.getInstance();
  // 得到当前的年月日
  int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY);
  int minute = calendar.get(Calendar.MINUTE);
  Log.e("TAG", hourOfDay + ":" + minute);
  new TimePickerDialog(this, new TimePickerDialog.OnTimeSetListener() {
  @Override
  public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
  Log.e("TAG_IN", hourOfDay + ":" + minute); // 设置后的时间
  }
  }, hourOfDay, minute, true).show();
 });

补充

1. 启动分线程

方式1:

 new Thread() {
  public void run() {
  // 此处在分线程执行
  }
 }.start();

方式2:

 new Thread(new Runnable() {
  public void run() {
  // 分线程执行
  }
 }).start();
2. 根据id查找View对象
  • 查找当前界面的View对象: findViewById(id)

  • 查找某个View对象的子View:view.findViewById(id)

3. 更新视图
  • 不能在分线程直接更新UI

  • 长时间工作只能在分线程执行

2.2 常用UI布局

概述

  • 布局本身是不能显示出任何数据,它可以包含一些子视图,并控制子视图的布局

  • 常用的Layout

LinearLayout 线性布局

  • 线性布局:用来控制其子View以水平或垂直方式展开显示

  • 重要属性:

    • orientation(方向)

    • layout_weight(权重)

理解LinearLayout权重

  • layout_weight(权重)的值

    • =0(默认值):指定多大空间就占据多大空间

    • >0:将父视图中的可用空间进行分割,值越大权重就越大,占据的比例就会越大

  • layout_weight的使用场景

    • 将布局的宽度或高度平均分成几个等份

    • 垂直方向上占用中间所有空间 或 水平方向上占据中间所有空间

均等分布

如要创建线性布局,让每个子视图使用大小相同的屏幕空间,请将每个视图的 android:layout_height 设置为 "0dp"(针对垂直布局),或将每个视图的 android:layout_width 设置为 "0dp"(针对水平布局)。然后,请将每个视图的 android:layout_weight 设置为 "1"

实例

 

 

 <?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">
 
  <EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:hint="To"/>
  <EditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:hint="Subject"/>
 
  <EditText
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:gravity="top"
  android:hint="Message"/>
 
  <LinearLayout
  android:orientation="horizontal"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">
 
  <Button
  android:text="Reset"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1" />
 
  <Button
  android:text="Send"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1" />
  </LinearLayout>
 </LinearLayout>

RelativeLayout 相对布局

  • 相对布局:用来控制其子View以相对定位的方式进行布局展示

  • 相对布局是最灵活、最强大,也是学习难度最大的布局

  • 相对布局相关属性比较多:

    • 兄弟视图之间:同方向对齐、反方向对齐

    • 与父视图之间:同方向对齐、居中

实例

 

 

 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 
  <EditText
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_alignParentStart="true"
  android:layout_alignParentEnd="true"
  android:hint="Message"
  android:id="@+id/et_1" />
 
  <Button
  android:text="ok"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_below="@id/et_1"
  android:layout_alignParentEnd="true"
  android:id="@+id/btn_1" />
 
  <Button
  android:text="cancel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_below="@id/et_1"
  android:layout_toStartOf="@id/btn_1"
  android:layout_marginEnd="10dp"
  android:id="@+id/btn_2" />
 </RelativeLayout>

FrameLayout 帧布局

  • 帧布局中的每一个子View都代表一个画面,默认以屏幕左上角作为(0, 0)坐标,按定义的先后顺序依次逐屏显示,后面出现的会覆盖前面的画面

  • 可以通过android:layout_grivity等属性来指定子视图的位置

gravity:控制当前视图的内容 / 子view

layout_grivity:控制当前视图自己

实例

 

 

 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 
  <TextView
  android:background="#33ffff"
  android:layout_gravity="center"
  android:layout_width="280dp"
  android:layout_height="280dp" />
  <TextView
  android:background="#33ccff"
  android:layout_gravity="center"
  android:layout_width="240dp"
  android:layout_height="240dp" />
  <TextView
  android:background="#33aaff"
  android:layout_gravity="center"
  android:layout_width="200dp"
  android:layout_height="200dp" />
  <TextView
  android:background="#3399ff"
  android:layout_gravity="center"
  android:layout_width="160dp"
  android:layout_height="160dp" />
  <TextView
  android:background="#3366ff"
  android:layout_gravity="center"
  android:layout_width="120dp"
  android:layout_height="120dp" />
  <TextView
  android:background="#3300ff"
  android:layout_gravity="center"
  android:layout_width="80dp"
  android:layout_height="80dp" />
 </FrameLayout>

2.3 常用视图标签属性

属性的划分

  • 针对任何View的属性

    • 常用的最基本属性

    • 内边距属性 padding

    • 外边距属性 margin

  • 只针对RelativeLayout的属性

    • 反方向对齐属性:to / above / below

    • 同方向对齐属性:align

    • 相对父视图的属性 alignparent / center

  • 只针对LinearLayout的属性

    • 权重属性 weight

    • 方向属性 orientation

常用基本属性

属性名作用
id 为控件指定相应的ID @+id/idname
layout_width 指定当前视图的宽度
layout_height 指定当前视图的高度
text 指定控件当中显示的文字
textSize 指定控件文字大小
background 指定该控件所使用的背景(图片 / 颜色)
layout_gravity 控件本身相对于父视图的位置
gravity 指定控件中的内容的基本位置

内边距与外边距

  • 内边距属性

    • android:padding

    • android:paddingLeft

    • android:paddingRight

    • android:paddingStart

    • android:paddingEnd

    • android:paddingTop

    • android:paddingBottom

  • 外边距属性

    • android:layout_margin

    • android:layout_marginLeft

    • android:layout_marginRight

    • android:layout_marginStart

    • android:layout_marginEnd

    • android:layout_marginTop

    • android:layout_marginBottom

对于从左到右(LTR)语言,start就是left,end就是right。对于从右到左(RTL)语言(例如,希伯来语,阿拉伯语,中东地区),end是left,start是right。如果您使用end和start属性,那么当您的布局在设置为RTL区域设置的设备上运行时,布局将会镜像,如果使用left和right则不会镜像。一般推荐使用start/end属性

配置

RTL布局默认是关闭的。

AndroidManifest.xml文件下<application>配置属性android:supportsRtl="true"。然后将相应视图标签设置属性android:layoutDirection="rtl"。例如,修改本章2.2相对布局:

 

 

注意:start 和 end 属性在 API Level 17(Android 4.2)才被添加。低于此版本不能使用。

同方向对齐和反方向对齐:相对兄弟视图

  • 同方向对齐属性

    • android:layout_alignLeft

    • android:layout_alignRight

    • android:layout_alignStart

    • android:layout_alignEnd

    • android:layout_alignTop

    • android:layout_alignBottom

  • 反方向对齐属性

    • android:layout_toLeftOf

    • android:layout_toRightOf

    • android:layout_toStartOf

    • android:layout_toEndOf

    • android:layout_above

    • android:layout_below

相对父视图定位

  • 与父视图同方向对齐属性

    • android:layout_alignParentStart

    • android:layout_alignParentEnd

    • android:layout_alignParentLeft

    • android:layout_alignParentRight

    • android:layout_alignParentTop

    • android:layout_alignParentBottom

  • 相对父视图居中属性

    • android:layout_centerInParent

    • android:layout_centerVertical

    • android:layout_centerHorizontal

2.4 ListView

  • ListView是一种用来显示多个可滑动项(Item)列表的ViewGroup

  • 需要使用Adapter集合数据和每一个Item所对应的布局动态适配到LIstView中显示

  • 显示列表:listView.setAdapter(adapter)

 

 

Adapter

 

 

  • ArrayAdapter:显示最简单的列表(文本),集合数据为List<String> 或 String[]

  • SimpleAdapter:显示复杂的列表,集合数据必须是List<Map<String, Object>>类型

  • BaseAdapter:显示复杂的列表,集合数据可以是任意类型的集合List<Xxx>

  • SimpleCursorAdapter:显示复杂的列表,集合数据是数据库查询结果集

ListView + ArrayAdapter

ArrayAdapter(Contxet context, int resource, T[] objects)

ArrayAdapter(Context context, int resource, List<T> objects)

  • context:上下文对象,一般为Activity对象

  • resource:Item的布局文件标识

  • objects:需要显示的集合数据(Array 或 List)

  // MainActivity.java
 public class MainActivity extends AppCompatActivity {
  private ListView lv_main;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 
  lv_main = findViewById(R.id.lv_main);
  // 准备集合数据
  String[] data = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"};
  // 准备Adapter对象
  ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.activity_item, data);
  // 设置Adapter显示列表
  lv_main.setAdapter(adapter);
  }
 
 }
 <!-- activity_main.xml -->
 <ListView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/lv_main"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 </ListView>
 
 <!-- activity_item.xml -->
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:textSize="20sp"
  android:gravity="center"
  android:layout_height="60dp">
 </TextView>

 

 

ListView + SimpleAdapter

SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)

  • context:上下文对象,一般为Activity对象

  • data:需要显示的数据集合

  • resource:Item的布局文件标识

  • from:map对象中的key的数组,用于得到对应的value

  • to:Item布局文件中的子view的id的数组

 

 

布局文件

 <!--activity_main.xml-->
 <?xml version="1.0" encoding="utf-8"?>
 <ListView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/lv_main"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 </ListView>
 
 <!--activity_item.xml-->
 <?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="wrap_content">
  <ImageView
  android:id="@+id/iv_icon"
  android:layout_width="100dp"
  android:layout_height="100dp"
  android:src="@drawable/bird" />
  <LinearLayout
  android:orientation="vertical"
  android:layout_height="100dp"
  android:gravity="center_vertical"
  android:layout_marginLeft="10dp"
  android:layout_width="wrap_content">
 
  <TextView
  android:text="TextView"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_marginBottom="16dp"
  android:id="@+id/tv_name" />
 
  <TextView
  android:text="TextView"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:id="@+id/tv_content" />
  </LinearLayout>
 </LinearLayout>

代码

 lv_main = findViewById(R.id.lv_main);
 // 准备集合数据
 List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
 Map<String, Object> map = new HashMap<String, Object>();
 map.put("icon", R.drawable.duck);
 map.put("name", "鸭子");
 map.put("content", "---");
 data.add(map);
 map = new HashMap<String, Object>();
 map.put("icon", R.drawable.cat);
 map.put("name", "猫");
 map.put("content", "---");
 data.add(map);
 ...
 map = new HashMap<String, Object>();
 map.put("icon", R.drawable.zhongli);
 map.put("name", "钟离");
 map.put("content", "---");
 data.add(map);
 // map对象中的key的数组,用于得到对应的value
 String[] from = {"icon", "name", "content"};
 // item 布局文件中子view的id的数组
 int[] to = {R.id.iv_icon, R.id.tv_name, R.id.tv_content};
 // 准备Adapter对象
 SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.activity_item, from, to);
 // 设置Adapter显示列表
 lv_main.setAdapter(adapter);

ListView + BaseAdapter

 class MyBaseAdapter extends BaseAdapter { // BaseAdapter抽象类 需要重写方法
 
  @Override
  public int getCount() { // 重要
  return 0;
  }
 
  @Override
  public Object getItem(int position) {
  return null;
  }
 
  @Override
  public long getItemId(int position) {
  return 0;
  }
 
  @Override
  public View getView(int position, View convertView, ViewGroup parent) { // 重要
  return null;
  }
 }

实现效果和 SimpleAdapter 一样,布局文件无需变动,下面是代码:

com/example/helloandroid/ShopInfo.java

 /**
  * 每行Item数据信息封装类
  */
 public class ShopInfo {
  private int icon;
  private String name;
  private String content;
 
  ...generate 空参构造函数 三个参数构造函数 getter setter toString
 }

mainActivity.java

 public class MainActivity extends AppCompatActivity {
  private ListView lv_main;
  private List<ShopInfo> data;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 
  lv_main = findViewById(R.id.lv_main);
  // 准备集合数据
  data = new ArrayList<ShopInfo>();
  data.add(new ShopInfo(R.drawable.duck, "鸭子", "---"));
  data.add(new ShopInfo(R.drawable.bird, "鸟", "---"));
  data.add(new ShopInfo(R.drawable.cat, "猫", "---"));
  data.add(new ShopInfo(R.drawable.juice, "果汁", "---"));
  data.add(new ShopInfo(R.drawable.orange, "橘子", "---"));
  data.add(new ShopInfo(R.drawable.spider, "蜘蛛", "---"));
  data.add(new ShopInfo(R.drawable.zhongli, "钟离", "---"));
  // 准备Adapter对象
  MyBaseAdapter adapter = new MyBaseAdapter();
  // 设置Adapter显示列表
  lv_main.setAdapter(adapter);
  }
 
  class MyBaseAdapter extends BaseAdapter {
  // 返回集合数据的数量
  @Override
  public int getCount() {
  return data.size();
  }
  // 返回指定下标对应的数据对象
  @Override
  public Object getItem(int position) {
  return data.get(position);
  }
  // 获取item视图id
  @Override
  public long getItemId(int position) {
  return 0;
  }
  /**
  * 返回指定下标所对应的Item的View对象
  * @param position 下标
  * @param convertView 可复用的缓存Item视图对象,前N+1为空 N为listview视图可同时显示的最大数据量
  * @param parent ListView对象
  * @return view
  */
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
  //如果没有复用的
  if(convertView==null) {
  // 加载Item的布局 得到view对象
  convertView = View.inflate(MainActivity.this, R.layout.activity_item, null);
  }
  // 根据position设置对应的数据
  ShopInfo si = data.get(position);
  ImageView iv = convertView.findViewById(R.id.iv_icon);
  TextView tv_name = convertView.findViewById(R.id.tv_name);
  TextView tv_content = convertView.findViewById(R.id.tv_content);
  iv.setImageResource(si.getIcon());
  tv_name.setText(si.getName());
  tv_content.setText(si.getContent());
  return convertView;
  }
  }
 }

2.5 Style 和 theme

样式(style)

  • 理解

    • 多个视图属性的集合,在写布局时,当多个视图有不少相同的属性时,可以把这些相同的属性放在一起在style.xml中定义成一个Style,而在布局文件中使用@style/style_name统一引用

  • 作用

    • 复用视图标签属性

  • 目标

    • 针对的是窗口中的某些视图

  • 系统样式

    • Android也定义了些系统样式可以使用

    • 使用系统样式:@android:style/xxx

定义res/values/style.xml

 <!--
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:gravity="center_vertical"
  android:textSize="20sp"
  android:layout_marginStart="10dp"
  android:drawableLeft="@android:drawable/star_big_on"
 -->
 <style name="tv_1">
  <item name="android:layout_width">match_parent</item>
  <item name="android:layout_height">wrap_content</item>
  <item name="android:gravity">center_vertical</item>
  <item name="android:textSize">20sp</item>
  <item name="android:layout_marginStart">10dp</item>
  <item name="android:drawableLeft">@android:drawable/star_big_on</item>
 </style>

使用

 <TextView
  android:text="远程销毁数据"
  style="@style/tv_1"
  android:id="@+id/textView2" />

主题(theme)

  • 理解

    • 主题的本质也是Style

    • styles.xml中定义,在manifest.xml中引用

  • 作用

    • 复用标签属性

  • 目标

    • 针对整个应用某个Activity的界面

  • 系统常用主题,activity.java需要改为继承activity

    • @android:style/Theme.Light.NoTitleBar:没有标题

    • @android:style/Theme.Light.NoTitleBar.Fullscreen:全屏

    • @android:style/Theme.Dialog:对话框

引用自定义主题样式

设置样式,必须命名Theme.AppCompat.xxx,否则报错。或者mainActivity改为继承activity。

 <style name="Theme.AppCompat.myTheme">
  <item name="android:textColor">#bfa</item>
 </style>

在manifest.xml使用,application或者activity都可以引用

 <activity
  android:name=".MainActivity"
  android:exported="true"
  android:theme="@style/Theme.AppCompat.myTheme"
  android:launchMode="singleTask"></activity>

3. 应用练习

3.1 显示应用程序列表

  • 功能描述

    • 此功能是手机卫士程序管理模块的一部分

    • 它以列表的形式列出手机中所有应用的信息

    • 点击某一项,提示选择的应用的名称

    • 长按某一项,删除当前行

  • 主要技术

    • 应用的整体布局与Item的布局

    • 获取手机中所有安装的应用信息集合(已定义)

    • 使用BaseAdapter显示列表信息

    • ListView的item点击响应

布局文件

layout/activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 
  <TextView
  android:text="所有应用"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:gravity="center"
  android:textSize="24sp"
  android:textColor="#fff"
  android:background="#BE78C6"
  android:padding="8dp"
  android:id="@+id/tv_title"
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent" />
 
  <ListView
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:id="@+id/lv_main"
  app:layout_constraintTop_toBottomOf="@+id/tv_title"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintBottom_toBottomOf="parent" />
 </androidx.constraintlayout.widget.ConstraintLayout>

layout/activity_item.xml

 <?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="wrap_content">
  <ImageView
  android:id="@+id/iv_icon"
  android:layout_width="60dp"
  android:layout_height="60dp"
  android:src="@drawable/cat" />
 
  <TextView
  android:text="应用名称"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_marginLeft="10dp"
  android:textSize="20sp"
  android:gravity="center_vertical"
  android:id="@+id/tv_name" />
 </LinearLayout>

java代码

com/example/helloandroid/AppInfo.java

 package com.example.helloandroid;
 
 import android.graphics.drawable.Drawable;
 
 import androidx.annotation.NonNull;
 
 /**
  * 应用信息的封装类
  */
 public class AppInfo {
  private Drawable icon;
  private String appName;
  private String packageName;
 
  public AppInfo(Drawable icon, String appName, String packageName) {
  this.icon = icon;
  this.appName = appName;
  this.packageName = packageName;
  }
 
  @NonNull
  @Override
  public String toString() {
  return "AppInfo{" +
  "icon=" + icon +
  ", appName='" + appName + '\'' +
  ", packageName='" + packageName + '\'' +
  '}';
  }
 
  public Drawable getIcon() {
  return icon;
  }
 
  public void setIcon(Drawable icon) {
  this.icon = icon;
  }
 
  public String getAppName() {
  return appName;
  }
 
  public void setAppName(String appName) {
  this.appName = appName;
  }
 
  public String getPackageName() {
  return packageName;
  }
 
  public void setPackageName(String packageName) {
  this.packageName = packageName;
  }
 
  public AppInfo() {
  }
 }
 

com/example/helloandroid/MainActivity.java

 package com.example.helloandroid;
 
 import androidx.appcompat.app.AppCompatActivity;
 
 import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.SimpleAdapter;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class MainActivity extends AppCompatActivity {
  private ListView lv_main;
  private List<AppInfo> data;
  private AppAdapter adapter;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 
  // 初始化成员变量
  lv_main = findViewById(R.id.lv_main);
  data = getAllAppInfo();
  adapter = new AppAdapter();
 
  lv_main.setAdapter(adapter);
  lv_main.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  /**
  * @param parent listView
  * @param view 当前行的视图对象
  * @param position 下标
  */
  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  Toast.makeText(MainActivity.this, data.get(position).getAppName(), Toast.LENGTH_SHORT).show();
  }
  });
  lv_main.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
  @Override
  public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
  data.remove(position);
  adapter.notifyDataSetChanged(); // 通知列表更新
  return true;
  }
  });
  }
 
  class AppAdapter extends BaseAdapter {
 
  @Override
  public int getCount() {
  return data.size();
  }
 
  @Override
  public Object getItem(int position) {
  return data.get(position);
  }
 
  @Override
  public long getItemId(int position) {
  return 0;
  }
 
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null) {
  convertView = View.inflate(MainActivity.this, R.layout.activity_item, null);
  }
  AppInfo appInfo = data.get(position);
  ImageView iv = convertView.findViewById(R.id.iv_icon);
  TextView tv = convertView.findViewById(R.id.tv_name);
  iv.setImageDrawable(appInfo.getIcon());
  tv.setText(appInfo.getAppName());
  return convertView;
  }
  }
 
  /**
  * 得到手机中所有应用信息的列表
  */
  protected List<AppInfo> getAllAppInfo() {
  List<AppInfo> list = new ArrayList<AppInfo>();
  // 得到应用的packageManager
  PackageManager packageManager = getPackageManager();
  // 创建一个主界面的intent
  Intent intent = new Intent();
  intent.setAction(Intent.ACTION_MAIN);
  intent.addCategory(Intent.CATEGORY_LAUNCHER);
  // 得到包含应用信息的列表
  @SuppressLint("QueryPermissionsNeeded") List<ResolveInfo> ResolveInfos = packageManager.queryIntentActivities(intent, 0);
 
  for(ResolveInfo ri: ResolveInfos) {
  // 得到包名
  String packageName = ri.activityInfo.packageName;
  // 得到图标
  Drawable icon = ri.loadIcon(packageManager);
  // 得到应用名称
  String appName = ri.loadLabel(packageManager).toString();
  // 封装应用信息对象
  AppInfo appInfo = new AppInfo(icon, appName, packageName);
  list.add(appInfo);
  }
  return list;
 
}

3.2 手机卫士主界面

  • 功能描述

    • 此功能是手机卫士主界面功能的一部分

    • 以3*3网格的形式显示功能模块列表

    • 点击某一项,提示选择的功能模块的名称

  • 主要技术

    • 应用的整体布局与Item的布局

    • 利用BaseAdapterGridView中网格列表信息

    • GridViewitem点击响应

注:没有教程对应图片资源,所以使用系统本身应用代替。此节主要是学习使用GridView

布局文件

layout/activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 
  <TextView
  android:text="所有应用"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:gravity="center"
  android:textSize="24sp"
  android:textColor="#fff"
  android:background="#BE78C6"
  android:padding="8dp"
  android:id="@+id/tv_title"
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent" />
 
  <GridView
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:id="@+id/gv_main"
  android:numColumns="3"
  android:verticalSpacing="10dp"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/tv_title" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

layout/activity_item.xml

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="80dp"
  android:layout_height="80dp">
  <ImageView
  android:id="@+id/iv_icon"
  android:layout_width="60dp"
  android:layout_gravity="center"
  android:layout_height="60dp"
  android:src="@drawable/cat" />
 
  <TextView
  android:text="应用名称"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:textSize="18sp"
  android:textAlignment="center"
  android:gravity="center_vertical"
  android:id="@+id/tv_name" />
 </LinearLayout>

java文件

AppInfo.java不变

将Adapter移到单独文件

com/example/helloandroid/MainAdapter.java

 package com.example.helloandroid;
 
 import android.content.Context;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import java.util.List;
 
 public class MainAdapter extends BaseAdapter {
  private final List<AppInfo> data;
  private final Context context;
 
  public MainAdapter(Context context, List<AppInfo> data) { // 构造器传参
  super();
  this.data = data;
  this.context = context;
  }
 
  @Override
  public int getCount() {
  return data.size();
  }
 
  @Override
  public Object getItem(int position) {
  return data.get(position);
  }
 
  @Override
  public long getItemId(int position) {
  return 0;
  }
 
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null) {
  convertView = View.inflate(context, R.layout.activity_item, null);
  }
  AppInfo appInfo = data.get(position);
  ImageView iv = convertView.findViewById(R.id.iv_icon);
  TextView tv = convertView.findViewById(R.id.tv_name);
  iv.setImageDrawable(appInfo.getIcon());
  tv.setText(appInfo.getAppName());
  return convertView;
  }
 }
 

com/example/helloandroid/MainActivity.java

 package com.example.helloandroid;
 
 import androidx.appcompat.app.AppCompatActivity;
 
 import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
 import android.widget.GridView;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.SimpleAdapter;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class MainActivity extends AppCompatActivity {
  private GridView gv_main;
  private List<AppInfo> data;
  private MainAdapter adapter;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 
  // 初始化成员变量
  gv_main = findViewById(R.id.gv_main);
  data = getAllAppInfo();
  adapter = new MainAdapter(MainActivity.this, data);
 
  gv_main.setAdapter(adapter);
  gv_main.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  /**
  * @param parent listView
  * @param view 当前行的视图对象
  * @param position 下标
  */
  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  Toast.makeText(MainActivity.this, data.get(position).getAppName(), Toast.LENGTH_SHORT).show();
  }
  });
  gv_main.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
  @Override
  public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
  data.remove(position);
  adapter.notifyDataSetChanged();
  return true;
  }
  });
  }
 
  /**
  * 得到手机中所有应用信息的列表
  */
  protected List<AppInfo> getAllAppInfo() {
  List<AppInfo> list = new ArrayList<AppInfo>();
  // 得到应用的packageManager
  PackageManager packageManager = getPackageManager();
  // 创建一个主界面的intent
  Intent intent = new Intent();
  intent.setAction(Intent.ACTION_MAIN);
  intent.addCategory(Intent.CATEGORY_LAUNCHER);
  // 得到包含应用信息的列表
  @SuppressLint("QueryPermissionsNeeded") List<ResolveInfo> ResolveInfos = packageManager.queryIntentActivities(intent, 0);
 
  for(ResolveInfo ri: ResolveInfos) {
  // 得到包名
  String packageName = ri.activityInfo.packageName;
  // 得到图标
  Drawable icon = ri.loadIcon(packageManager);
  // 得到应用名称
  String appName = ri.loadLabel(packageManager).toString();
  // 封装应用信息对象
  AppInfo appInfo = new AppInfo(icon, appName, packageName);
  list.add(appInfo);
  }
  return list;
  }
 }

 

 

 
posted @ 2022-10-12 15:15  少年と山  阅读(67)  评论(0)    收藏  举报