在APP中实现日历的操作对于很多人来说或许都不太容易,因为根据要求的不同,日历的样式和显示的功能也不太一样,特别是对于一些初写代码,不太熟练写自定义接口的朋友,比如说我。 我们也会在网上经常看到一些日历的Demo,但有时候也不太符合我们的要求,最近正好在APP中有一个考勤统计的日历操作,也是在同事的帮助下完成的,我给记录一下,希望对你也有帮助。好了!我们先看一下效果图,然后再谈具体的操作。
我们对功能进行简单介绍一下:这是一根据日历展示的外勤统计的页面,由头标题,年月检索栏,日历,当前日期,检索出来的统计列表组成。然后我们对这些进行一一的了解。
首先是头标题,这个就不用介绍了吧 ,简直是 so easy ! 我们直接看代码 titlebar_blue.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="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="49dp"
android:layout_gravity="center"
android:background="#006ad8">
<ImageView
android:id="@+id/iv_back"
android:layout_width="45dp"
android:layout_height="48dp"
android:layout_marginLeft="5dp"
android:padding="15dp"
android:scaleType="centerInside"
android:visibility="gone"
android:src="@drawable/back" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text=""
android:textColor="@color/colorWhite"
android:textSize="17sp" />
</RelativeLayout>
</LinearLayout>
接下来我们看一下年月检索栏,这个功能的实现也是使用了第三方 TimePickerView 实现的 ,但是并没有直接的去依赖,而是将生成一个 aar包 然后再进行依赖,这样可以减小apk的大小,我们看一下图片,以及依赖。
这里我们对时间选择器 TimePickerView 不进行多介绍,如果有兴趣的话你可以学习一下,我们只展示下部分功能,具体解释已经在代码中展现:
tv_title_date.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//年月检索 type具有多种类型,可以展示年月日,年月,年月日时分秒等。你可以搜索一下
timePickerView = new TimePickerView(context, TimePickerView.Type.YEAR_MONTH);//年月
//控制时间范围
Calendar calendar = Calendar.getInstance();
//20 代表检索时下上可以滑动上20年,下20年 滑动的范围
timePickerView.setRange(calendar.get(Calendar.YEAR) - 20, calendar.get(Calendar.YEAR) + 20);//要在setTime 之前才有效果哦
timePickerView.setCyclic(true);
//展示当前的年月
timePickerView.setTime(initDate);
//点击阴暗部分,是否可以取消
timePickerView.setCancelable(false);
//在时间选择器上进行选择,日历展示相对的月份
timePickerView.setOnTimeSelectListener(new TimePickerView.OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date) {
initDate = date;
//获取选择的年月赋值给年月检索栏
tv_title_date.setText( DateUtils.getTimeYM(date));
Calendar c = Calendar.getInstance();
c.setTime(date);
if (DateUtils.getTimeYM(date).equals(DateUtils.currentYM())) {//得到日期字符串//获取当前年月字符串
//这是当年当月 选中当天(日期)
setYearMonthDay(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, DateUtils.getToday_DAY());
}else {
//非当年当月 不选中任何天
setYearMonthDay(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, -1);
}
}
});
timePickerView.show();
}
});
然后我们就看一下自定义的日历,先看下展示日历的view_yearmonthdatechoose.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"
android:background="@color/white"
android:orientation="vertical">
<!--年月检索栏-->
<TextView
android:id="@+id/tv_title_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:drawableLeft="@drawable/date_blue"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:text=""
android:textColor="@color/titlebar_bg_blue"
android:textSize="12sp" />
<View
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:layout_marginTop="10dp"
android:background="@color/tv_f3" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="日"
android:textColor="@color/tv_22"
android:textSize="13sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="一"
android:textColor="@color/tv_22"
android:textSize="13sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="二"
android:textColor="@color/tv_22"
android:textSize="13sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="三"
android:textColor="@color/tv_22"
android:textSize="13sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="四"
android:textColor="@color/tv_22"
android:textSize="13sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="五"
android:textColor="@color/tv_22"
android:textSize="13sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="六"
android:textColor="@color/tv_22"
android:textSize="13sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:background="@color/tv_f3" />
<!--日历列表AutoFitGridView 是不可滑动的GridView-->
<calendarview.itcast.zz.calendarview.AutoFitGridView
android:id="@+id/gv_month"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:numColumns="7"
android:verticalSpacing="8dp">
</calendarview.itcast.zz.calendarview.AutoFitGridView>
</LinearLayout>
然后我们看一下代码的实现:YearMonthDateChooseView
/**
* 外勤统计的日历选择器
*/
public class YearMonthDateChooseView extends LinearLayout {
private Context context;
private TextView tv_title_date;//标题年月份
private GridView gv_month;//月份的布局
private YearMonthDateAdapter yearMonthDateAdapter;
private int mYear;//几几年
private int mMonth;//几几月
private int mDay;//几几号
private LinearLayout contentView;
private TimePickerView timePickerView;
private Date initDate;
public YearMonthDateChooseView(Context context) {
this(context,null);
}
public YearMonthDateChooseView(Context context, AttributeSet attrs) {
this(context, attrs,-1);
}
public YearMonthDateChooseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
this.context = context;
// 获取对象
Calendar nowCalendar = Calendar.getInstance();
mYear = nowCalendar.get(Calendar.YEAR);
mMonth = nowCalendar.get(Calendar.MONTH)+1;//0 - 11 所以加上1
mDay = nowCalendar.get(Calendar.DAY_OF_MONTH);
initDate = new Date();
initView();
initData();
}
private void initView(){
contentView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.view_yearmonthdatechoose,null);
tv_title_date = (TextView) contentView.findViewById(R.id.tv_title_date);
gv_month = (GridView) contentView.findViewById(R.id.gv_month);
}
private void initData() {
gv_month.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
tv_title_date.setText(mYear + "-" + mMonth);
if (context instanceof Activity){//context是否是Activity 的实例
yearMonthDateAdapter = new YearMonthDateAdapter((Activity) context,mYear,mMonth,mDay);
}else{
yearMonthDateAdapter = new YearMonthDateAdapter(context,mYear,mMonth,mDay);
}
yearMonthDateAdapter.setOnMyItemClickListener(new YearMonthDateAdapter.OnMyItemClickListener() {
@Override
public void onMyItemClick(int postion, int mDay) {
tv_title_date.setText(mYear + "-" + mMonth);
//根据列明获取周
String weekName = DateUtils.getWeekName(mYear,mMonth,mDay);
if (onDateClickListener != null) {
onDateClickListener.onDateCLick(mYear, mMonth, mDay,weekName);
}else {
Log.d("error:--", "onMyItemClick() returned: " + "onDateClickListener为null");
}
}
});
gv_month.setAdapter(yearMonthDateAdapter);
contentView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
this.removeAllViews();
this.addView(contentView);
tv_title_date.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//年月检索 type具有多种类型,可以展示年月日,年月,年月日时分秒等。你可以搜索一下
timePickerView = new TimePickerView(context, TimePickerView.Type.YEAR_MONTH);//年月
//控制时间范围
Calendar calendar = Calendar.getInstance();
//20 代表检索时下上可以滑动上20年,下20年 滑动的范围
timePickerView.setRange(calendar.get(Calendar.YEAR) - 20, calendar.get(Calendar.YEAR) + 20);//要在setTime 之前才有效果哦
timePickerView.setCyclic(true);
//展示当前的年月
timePickerView.setTime(initDate);
//点击阴暗部分,是否可以取消
timePickerView.setCancelable(false);
//在时间选择器上进行选择,日历展示相对的月份
timePickerView.setOnTimeSelectListener(new TimePickerView.OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date) {
initDate = date;
//获取选择的年月赋值给年月检索栏
tv_title_date.setText( DateUtils.getTimeYM(date));
Calendar c = Calendar.getInstance();
c.setTime(date);
if (DateUtils.getTimeYM(date).equals(DateUtils.currentYM())) {//得到日期字符串//获取当前年月字符串
//这是当年当月 选中当天(日期)
setYearMonthDay(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, DateUtils.getToday_DAY());
}else {
//非当年当月 不选中任何天
setYearMonthDay(c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, -1);
}
}
});
timePickerView.show();
}
});
}
/**
* 设置年月日
* @param year
* @param month
* @param day
*/
public void setYearMonthDay(int year,int month ,int day){
mYear = year;
mMonth = month;
mDay = day;
initData();
}
/**
* 获取默认年
* @return
*/
public int getmYear() {
return mYear;
}
/**
* 获取默认月份
* @return
*/
public int getmMonth() {
return mMonth;
}
/**
* 获取默认日期
* @return
*/
public int getmDay() {
return mDay;
}
//自定义日期点击监听(用于外勤统计)点击日期查询签到列表
public interface OnDateClickListener{
void onDateCLick(int year, int month, int day, String week);
}
private OnDateClickListener onDateClickListener;
public void setOnDateClickListener(OnDateClickListener onDateClickListener) {
this.onDateClickListener = onDateClickListener;
}
}
YearMonthDataAdapter日历展示
布局文件Item_calendar_year_month.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="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_item"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@color/white">
<TextView
android:id="@+id/tv_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:button="@null"
android:clickable="false"
android:focusable="false"
android:gravity="center"
android:text=""
android:textSize="13sp" />
<!--android:textColor="@color/sl_col_black_blue"-->
<!--android:background="@drawable/sl_clendar_month"-->
</RelativeLayout>
</LinearLayout>
代码展示:
/**
* 自定义日期的 月份适配
*/
public class YearMonthDateAdapter extends BaseAdapter {
private Activity activity;
private int mYear;//几几年
private int mMonth;//几几月
private int mDay;//几几号
private int days;//该月有多少天
private int firstDayOfWeek;//该月第一天是星期几
private List<String> datas;
public YearMonthDateAdapter(Context context, int mYear, int mMonth, int mDay) {
this.activity = (Activity) context;
this.mYear = mYear;
this.mMonth = mMonth;
this.mDay = mDay;
// 设置月份的数据表
setDatas(mYear, mMonth, mDay);
}
public YearMonthDateAdapter(Activity activity, int mYear, int mMonth, int mDay) {
this.activity = activity;
this.mYear = mYear;
this.mMonth = mMonth;
this.mDay = mDay;
// 设置月份的数据表
setDatas(mYear, mMonth, mDay);
}
@Override
public int getCount() {
return firstDayOfWeek + days;
}
@Override
public Object getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
Holder vh;
if (convertView == null) {
convertView = LayoutInflater.from(activity).inflate(R.layout.item_calendar_year_month, null);
vh = new Holder();
vh.tv_day = (TextView) convertView.findViewById(R.id.tv_day);
vh.rl_item = (RelativeLayout) convertView.findViewById(R.id.rl_item);
convertView.setTag(vh);
} else {
vh = (Holder) convertView.getTag();
}
// 月份第一天前的那几个item不能点击
if (mDay != -1 && position == firstDayOfWeek + mDay - 1) {//position 0——...
vh.tv_day.setBackgroundResource(R.drawable.circle_day);
vh.tv_day.setTextColor(activity.getResources().getColor(R.color.titlebar_bg_blue));
} else {
vh.tv_day.setBackgroundColor(activity.getResources().getColor(R.color.white));
vh.tv_day.setTextColor(activity.getResources().getColor(R.color.tv_22));
}
vh.tv_day.setText(datas.get(position));
vh.rl_item.setOnClickListener(new View.OnClickListener() {//当月一号前的几天不能点击
@Override
public void onClick(View v) {
if (position >= firstDayOfWeek) {
mDay = Integer.parseInt(datas.get(position));
if (onMyItemClickListener != null) {//可以点
onMyItemClickListener.onMyItemClick(position, mDay);
} else {
Log.d("error:", "onClick() returned: " + "onMyItemClickListener为null");
}
notifyDataSetChanged();
} else {
//不能点
}
}
});
return convertView;
}
private class Holder {
RelativeLayout rl_item;
TextView tv_day;
}
/**
* 设置月份的数据表
*
* @param year
* @param month
* @param day
*/
public void setDatas(int year, int month, int day) {
//通过年份和月份 得到当月的日子个数(一个月多少天)
days = DateUtils.getMonthDays(year, month);
//getFirstDayWeek 返回当前月份几号位于周几
firstDayOfWeek = DateUtils.getFirstDayWeek(year, month, 1);//第一天是周几
datas = new ArrayList<>();
//先添加空白天数 firstDayOfweek是几 就添加几天
for (int i = 0; i < firstDayOfWeek; i++) {
datas.add(" ");
}
for (int i = 1; i <= days; i++) {
datas.add("" + i);
}
}
/**
* 得到选中的天的day
*
* @return 23
*/
public int getMday() {
return mDay;
}
/**
* 得到选中的月份
*
* @return
*/
public int getmMonth() {
return mMonth;
}
/**
* 得到选中的年份
*
* @return
*/
public int getmYear() {
return mYear;
}
//自定义日期点击监听(Gridview)中
public interface OnMyItemClickListener {
void onMyItemClick(int position, int mDay);
}
private OnMyItemClickListener onMyItemClickListener;
public void setOnMyItemClickListener(OnMyItemClickListener onMyItemClickListener) {
this.onMyItemClickListener = onMyItemClickListener;
}
}
不可滑动的GridView:
/**
* 不滚动的gridView,自适应高度
* @author Administrator
*
*/
public class AutoFitGridView extends GridView {
public AutoFitGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public AutoFitGridView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public AutoFitGridView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
/**
* 设置不滚动
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
接下来我们来看看MainActivity的展示:
<?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"
android:background="@color/tv_f3"
android:orientation="vertical"
tools:context="calendarview.itcast.zz.calendarview.MainActivity">
//标题
<include layout="@layout/titlebar_blue" />
//自定义的日历
<calendarview.itcast.zz.calendarview.YearMonthDateChooseView
android:id="@+id/calendar_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</calendarview.itcast.zz.calendarview.YearMonthDateChooseView>
//当前日期
<TextView
android:id="@+id/tv_date"
android:layout_width="match_parent"
android:layout_height="35dp"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:text=""
android:textColor="@color/tv_33"
android:textSize="14sp" />
//查询到的统计列表展示
<android.support.v7.widget.RecyclerView
android:id="@+id/recycleview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/tv_f3">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ImageView iv_back;
private TextView tv_title;
private YearMonthDateChooseView calendar_view;
private TextView tv_date;
private RecyclerView recycleview;
private String date;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initListener();
}
private void initView() {
iv_back = (ImageView) findViewById(R.id.iv_back);
tv_title = (TextView) findViewById(R.id.tv_title);
calendar_view = (YearMonthDateChooseView) findViewById(R.id.calendar_view);
tv_date = (TextView) findViewById(R.id.tv_date);
recycleview = (RecyclerView) findViewById(R.id.recycleview);
recycleview.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
}
private void initData() {
iv_back.setVisibility(View.VISIBLE);
tv_title.setText("外勤统计");
//默认
tv_date.setText(calendar_view.getmYear() + "/" + calendar_view.getmMonth() + "/" +
calendar_view.getmDay() + " " + DateUtils.getWeekName(calendar_view.getmYear(), calendar_view.getmMonth(), calendar_view.getmDay()));
date = calendar_view.getmYear() + "-" + calendar_view.getmMonth() + "-" + calendar_view.getmDay();
// 获取外勤统计
getStatistic(date);
}
private void initListener() {
iv_back.setOnClickListener(this);
//自定义日期点击监听
calendar_view.setOnDateClickListener(new YearMonthDateChooseView.OnDateClickListener() {
@Override
public void onDateCLick(int year, int month, int day, String week) {
tv_date.setText(year+"/"+month+"/"+day+" "+week);
date = year + "-" + month + "-" + day;
getStatistic(date);
}
});
}
// 获取外勤统计
private void getStatistic(String date) {
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.iv_back:
finish();
break;
}
}
}
获取日期的公共类DataUtils:
public class DateUtils {
/**
* 通过年份和月份 得到当月的日子个数
*
* @param year
* @param month
* @return
*/
public static int getMonthDays(int year, int month) {
// month++;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
return 29;
} else {
return 28;
}
default:
return -1;
}
}
/**
* 返回当前月份几号位于周几
*
* @param year 年份
* @param month 月份,传入系统获取的,不需要正常的
* @return 日:0 一:1 二:2 三:3 四:4 五:5 六:6
*/
public static int getFirstDayWeek(int year, int month,int day) {
Calendar calendar = Calendar.getInstance();
calendar.set(year, month-1, day);
return calendar.get(Calendar.DAY_OF_WEEK)-1;
}
/**
* 根据列明获取周
*
* @param
* @return
*/
public static String getWeekName(int year, int month, int day) {
int column = getFirstDayWeek(year, month, day);
switch (column) {
case 0:
return "周日";
case 1:
return "周一";
case 2:
return "周二";
case 3:
return "周三";
case 4:
return "周四";
case 5:
return "周五";
case 6:
return "周六";
default:
return "";
}
}
/**
* 得到日期字符串
* @param date
* @return 年月日时分秒
*/
public static String getTime(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
return format.format(date);
}
/**
* 得到日期字符串
* @param date
* @return 年月日
*/
public static String getTimeYMD(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
return format.format(date);
}
/**
* 得到日期字符串
* @param date 年月
* @return
*/
public static String getTimeYM(Date date){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
return format.format(date);
}
/**
* 得到日期字符串
* @param date 年
* @return
*/
public static String getTimeY(Date date){
SimpleDateFormat format = new SimpleDateFormat("yyyy");
return format.format(date);
}
/**
* 获取当前年月日字符串
* @return
*/
public static String currentYMD(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
return format.format(new Date());
}
/**
* 获取当前年月字符串
*
* @return
*/
public static String currentYM(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
return format.format(new Date());
}
/**
* 通过日期字符串获取日期对象
* @param dateStr
* @return
*/
public static Date Str2DateYMD(String dateStr) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date ;
try{
date = format.parse(dateStr);
}catch (Exception e){
Log.d("DateUtils.Str2DateYMD()","Str2DateYMD() returned: " + e.toString() );
date = new Date();
}
return date;
}
/**
* 获取当前所在月份
* @return
*/
public static int getToday_MONTH(){
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
return calendar.get(Calendar.MONTH);
}
/**
* 获取当前所在天
* @return
*/
public static int getToday_DAY(){
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
return calendar.get(Calendar.DAY_OF_MONTH);
}
}
OK! 就是这样,你还有什么不懂的吗?
不要忘了“顶”一下啊哦!!
另外你也可以了解下"自定义view——带标签的CalendarView"http://blog.csdn.net/sinat_26710701/article/details/72772163