本人仓库地址:https://github.com/zechaowei/Android-Railway-Ticket-System
完成Android的基础学习,并根据视频教学内容完善项目功能;
点击APP后,出现三秒该画面;跳转到登录页面
登陆页面的三个图片自行选择
<?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"
android:paddingRight="16dp"
android:paddingLeft="16dp">
<ImageView
android:id="@+id/people"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/icon_people"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"/>
<!--设置用户栏-->
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@id/people"
android:layout_marginTop="80dp"
android:hint="用户名"
android:textSize="20sp"
android:textColor="#FFAD33"
android:maxLines="1"
android:drawableLeft="@drawable/user"/>
<!--密码栏-->
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@id/username"
android:layout_marginTop="40dp"
android:hint="密码"
android:inputType="textPassword"
android:textSize="20sp"
android:textColor="#FFAD33"
android:maxLines="1"
android:drawableLeft="@drawable/password"/>
<!--登录按钮-->
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/password"
android:layout_marginTop="80dp"
android:text="登录"
android:background="@drawable/btn_press_blue"
android:textColor="#FFFFFF"
android:textSize="25sp"/>
<!--自动登录-->
<CheckBox
android:id="@+id/autoLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btnLogin"
android:layout_marginTop="30dp"
android:text="自动登录"
android:textSize="15sp" />
<!--忘记密码-->
<TextView
android:id="@+id/tvForgetPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="忘记密码?"
android:textColor="#0000FF"
android:textSize="15sp"
android:layout_alignRight="@+id/btnLogin"
android:layout_alignBaseline="@+id/autoLogin" />
</RelativeLayout>
登录的用户名和密码已经“写死”,用户名:”admin
“,密码:”123456
“,输入后选择自动登录;
点击登录后,跳转到主页面
首先进入是的车票
、订单
、我的
三个选项页面
首先设定布局文件
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ticket.StationActivity">
<ListView
android:id="@+id/station_list"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#E8E8E8"
android:layout_weight="1"
android:cacheColorHint="#00000000"
android:scrollbars="none" />
<com.example.railwayticketingsystem.ticket.LetterListView
android:id="@+id/letterListView"
android:layout_height="match_parent"
android:layout_width="30dp">
</com.example.railwayticketingsystem.ticket.LetterListView>
</LinearLayout>
接下来实现java部分的代码:
public class StationActivity extends AppCompatActivity {
private BaseAdapter adapter;
private ListView mStationListView;
private TextView overlay;
private LetterListView letterListView;
private HashMap<String, Integer> alphaIndexer;
private String[] sections;
private Handler handler;
private OverlayThread overlayThread;
private ArrayList<Station> stations;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉标题行
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_station);
mStationListView = findViewById(R.id.station_list);
LetterListView letterListView = findViewById(R.id.letterListView);
//当字母有变化了,监听一下
letterListView.setOnTouchingLetterChangedListener(new LetterListViewListener());
//车站的数据
stations = StationUtils.getAllStations(this);
alphaIndexer = new HashMap<String, Integer>();
handler = new Handler();
overlayThread = new OverlayThread();
initOverlay();
//显示一堆车站
adapter = new ListAdapter(this, stations);
mStationListView.setAdapter(adapter);
mStationListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Station station = stations.get(position);
String name = station.getStation_name();
Toast.makeText(StationActivity.this, "name: "+name, Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra("name",name);
setResult(300,intent);
finish();
}
});
}
//点击右侧的字母,自动动定位到左侧 字母开始的车站
private class LetterListViewListener implements
LetterListView.OnTouchingLetterChangedListener {
//选中的字母
@Override
public void onTouchingLetterChanged(final String s) {
if (alphaIndexer.get(s) != null) {
int position = alphaIndexer.get(s);
mStationListView.setSelection(position);//定位字母对应的开始车站的位置
overlay.setText(sections[position]);
overlay.setVisibility(View.VISIBLE);
handler.removeCallbacks(overlayThread);
handler.postDelayed(overlayThread, 1500);
}
}
}
private class ListAdapter extends BaseAdapter {
private LayoutInflater inflater;
private List<Station> list;
public ListAdapter(Context context, List<Station> list) {
this.inflater = LayoutInflater.from(context);
this.list = list;
alphaIndexer = new HashMap<String, Integer>();
sections = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
// 当前汉语拼音首字母
String currentStr = list.get(i).getSort_order();
// 上一个汉语拼音首字母,如果不存在为" "
String previewStr = (i - 1) >= 0 ? list.get(i - 1)
.getSort_order() : " ";
if (!previewStr.equals(currentStr)) {
String name = list.get(i).getSort_order();
alphaIndexer.put(name, i);
sections[i] = name;
}
}
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater
.inflate(R.layout.station_list_item, null);
holder = new ViewHolder();
holder.alpha = (TextView) convertView.findViewById(R.id.alpha);
holder.name = (TextView) convertView.findViewById(R.id.name);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.name.setText(list.get(position).getStation_name());
String currentStr = list.get(position).getSort_order();
String previewStr = (position - 1) >= 0 ? list.get(position - 1) .getSort_order() : " ";
if (!previewStr.equals(currentStr)) {
holder.alpha.setVisibility(View.VISIBLE);
holder.alpha.setText(currentStr);
} else {
holder.alpha.setVisibility(View.GONE);
}
return convertView;
}
private class ViewHolder {
TextView alpha;
TextView name;
}
}
// 初始化汉语拼音首字母弹出提示框(TextView)
private void initOverlay() {
LayoutInflater inflater = LayoutInflater.from(this);
overlay = (TextView) inflater.inflate(R.layout.station_overlay, null);
overlay.setVisibility(View.INVISIBLE);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
WindowManager windowManager = (WindowManager) this
.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(overlay, lp);
}
private class OverlayThread implements Runnable {
@Override
public void run() {
overlay.setVisibility(View.GONE);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Intent intent = getIntent();
intent.putExtra("name", "");
setResult(200, intent);
finish();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
实现了出发城市和到达城市之间的动画播放,并且点击可以选择城市。
支持最右边侧面根据字母大写选择符合条件的城市。
private void selectTime() {
String srtTvFromStartTime = tvFromStartTime.getText().toString();
//2023/10/1
srtTvFromStartTime = srtTvFromStartTime.split(" ")[0];
String[] time = srtTvFromStartTime.split("/");
//选择出发时间
DatePickerDialog dialog = new DatePickerDialog(getActivity(), new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) {
Date selectDate = new Date(year - 1900, month, dayOfMonth);
String weekday = DateUtils.formatDateTime(getActivity(), selectDate.getTime(), DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY);
Toast.makeText(getActivity(), "weekday:" + weekday, Toast.LENGTH_SHORT).show();
Log.d("weekday", weekday);
//选择日期后,调用此事件
tvFromStartTime.setText("" + year + "/" + (month + 1) + "/" + dayOfMonth + " " + weekday);
}
}, Integer.parseInt(time[0]),
Integer.parseInt(time[1]) - 1,//月份从0开始
Integer.parseInt(time[2]));
dialog.show();
}
选择完以后,在出发日期一栏显示选择的结果,并且Toast提示选择的日期:
点击查询,直接跳转到如下页面,其中的数据都是“写死”的,无法动态修改。预定
按钮可以点击,点击后跳转到下一个页面
点击预定
按钮,跳转到如下页面:
该页面内容依旧“写死”,并且可以选择添加到乘车人,可以添加成功:
选择需要添加到乘车人员,其中数据利用for循环随机生成的数据,暂时没有动态实现选择。点击添加联系人
按钮即可跳转至上一个页面,并且可以显示选择的结果:
点击提交,即可跳转到主页面。
最后一个图片只是因为自己选择的虚拟机比较大,UI设计根据视频操作来后,UI界面不美观,自行添加图片。
只是实现这个页面,并没有设计UI以及具体功能,视频中只是提了几句,需要读者自己实现;
这里自己也只是实现该页面,没有后续操作。
页面展示:
视频教学中提到了我的联系人设计,其中我的账户
功能和我的联系人
功能部分及其相似,视频中并没有过多的介绍,读者可以参考我的联系人
部分实现我的账户
页面的功能。
同时我的密码
页面视频中也并未实现,也只是提及UI页面设计以及如何实现,需要读者自己实现
退出登录
功能:实现了登录逻辑的基本需要,在视频前中期有详细讲过。
每个联系人点击进去后都可以修改,并且利用for循环随机写入20个人的数据。点击张三0,跳转到如下页面:
这里实现了左上角的返回按钮的点击,以及右上角的删除人员的部分功能实现,只是现实图标如何操作,并没有真正删除张三0;初次之外,可以修改姓名
、乘客类型
和电话
三个选项。在讲解该功能结束后,提及到我的密码
功能如何实现。
接下来修改乘客类型:
教学视频中并未实现修改电话号码的功能,但是读者可以根据修改姓名功能实现该功能。
除此之外,点击保存按钮,并不会正真意义上的保存,只能点击。
private void startTranslate() {
int duration = 500;
//启动一个动画
TranslateAnimation animationLeft = new TranslateAnimation(0, 320, 0, 0);
//动画的速度
animationLeft.setInterpolator(new DecelerateInterpolator());
//动画持续的时间
animationLeft.setDuration(duration);
//出发城市,向右移动
tvStationFrom.startAnimation(animationLeft);
//启动一个动画
TranslateAnimation animationRight = new TranslateAnimation(0, -320, 0, 0);
//动画的速度
animationRight.setInterpolator(new DecelerateInterpolator());
//动画持续的时间
animationRight.setDuration(duration);
animationRight.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
//当动画执行完成,需要交换出发城市、到达城市
@Override
public void onAnimationEnd(Animation animation) {
CharSequence temp = tvStationFrom.getText();
tvStationFrom.setText(tvStationTo.getText());
tvStationTo.setText(temp);
}
});
//到达城市,向左移动
tvStationTo.startAnimation(animationRight);
}
并在onClick
函数中调用该方法:
@Override
public void onClick(View view) {
//交换站点
if (view.getId() == R.id.ivWf) {
startTranslate(); //调用动画函数
} else if (view.getId() == R.id.tvFromStartTime) {
selectTime();
} else if (view.getId() == R.id.tvStationFrom || view.getId() == R.id.tvStationTo) {
Intent intent = new Intent(getActivity(), StationActivity.class);
getActivity().startActivityForResult(intent, view.getId());
} else if (view.getId() == R.id.button1) {
Intent intent = new Intent(getActivity(), TicketBuy2Activity.class);
getActivity().startActivity(intent);
//将查询的出发城市、到达城市记录到SQLite数据库
logStation();
}
}
参考文章:Android利用zxing生成二维码
使用ZXing创建二维码
- 导入依赖
- 布局文件中绘制ImageView
- 通过BitMap类型构造ImageView的内容
添加依赖:https://mvnrepository.com/artifact/com.google.zxing/core/3.5.0
// https://mvnrepository.com/artifact/com.google.zxing/core
implementation 'com.google.zxing:core:3.5.0'
OrderActivity代码
public class OrderActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order);
ImageView qrCode = findViewById(R.id.qrCode);
Bitmap bitmap = createQRCodeImage("请支付100元",280,280);
qrCode.setImageBitmap(bitmap); //将内存中的数据渲染到ImageView中
}
public Bitmap createQRCodeImage(String txt, int w, int h)
{
Bitmap bitmap = null;
try
{
//判断URL合法性
if (txt == null || "".equals(txt) || txt.length() < 1)
{
return null;
}
Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
//图像数据转换,使用了矩阵转换
BitMatrix bitMatrix = new QRCodeWriter().encode(txt, BarcodeFormat.QR_CODE, w, h, hints);
int[] pixels = new int[w * h];
//下面这里按照二维码的算法,逐个生成二维码的图片,
//两个for循环是图片横列扫描的结果
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
if (bitMatrix.get(x, y))
{
pixels[y * w + x] = 0xff000000;
}
else
{
pixels[y * w + x] = 0xffffffff;
}
}
}
//生成二维码图片的格式,使用ARGB_8888
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
//显示到我们的ImageView上面
}
catch (WriterException e)
{
e.printStackTrace();
}
return bitmap;
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".OrderActivity"
android:background="#FFFFFF">
<TextView
android:id="@+id/tv_pay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请支付车票价格"
android:textSize="40sp"
android:gravity="center"
android:layout_centerHorizontal="true"/>
<ImageView
android:id="@+id/qrCodeBackground"
android:layout_width="320dp"
android:layout_height="320dp"
android:background="@drawable/qrcode_background"
android:layout_alignParentBottom="true"
android:layout_marginBottom="80dp"
android:gravity="center"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/qrCode"
android:layout_width="280dp"
android:layout_height="280dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="100dp"
android:gravity="center"
android:layout_centerHorizontal="true" />
</RelativeLayout>
渲染文件 使支付背景为圆角蓝色正方形
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blue"/>
<corners android:radius="20dp"/>
</shape>
- 功能是否完成
private void queryStationHistory() {
String fileName = "my_station";
MyStationDataBaseOpenHelper helper = new MyStationDataBaseOpenHelper(getActivity(), fileName, null, currentVersion);
SQLiteDatabase readDB = helper.getReadableDatabase();
Cursor c = readDB.rawQuery("select id, station_from, station_to from station_log order by id desc", null);
int row = 1;
while (c.moveToNext()) {
@SuppressLint("Range") String from = c.getString(c.getColumnIndex("station_from"));
@SuppressLint("Range") String to = c.getString(c.getColumnIndex("station_to"));
if (row == 1) {
tvft1.setText(from + "---" + to);
} else {
tvft2.setText(from + "---" + to);
}
if (row >= 2){
break;
}
row++;
}
c.close();
readDB.close();
helper.close();
}
注意:需要在全局调用onResume函数,否则点击查询的时候,历史查询不会及时更新(此部分需要了解android的生命周期)
@Override
public void onResume() {
super.onResume();
Log.d("TicketFragment", "=======onResume=========");
queryStationHistory();
}
当点击“天津”和“昂昂溪”后,点击查询后,退出后续界面,可以在查询历史页面看见之前点击的查询界面。