Android創建一個簡易課程表APP

目標

簡單的課程表APP,顯示課程,學期以及第幾周,效果圖如下:
lesson

問題與解決

  1. 動態添加控件時,對於可以指定style的構造函數如View(Context, AttributeSet, defStyleAttr: Int, defStyleRes: Int),需要使用new View(new ContextThemeWrapper(this, styleRes))方式
  2. 上述方式創建的控件,只有style中的少數值如padding, textColor等有效果,並不是所有值都有效
  3. GridLayout中指定weight時,空白單元格也需要添加控件,否則會出現空白行列消失的問題
  4. GridLayout動態添加控件時需要設置LayoutParameters.rowSpec與columnSpec,具體方法爲
    // ROW_WEIGHT 與 COLUMN_WEIGHT 爲float型,如1.0f
    ridLayout.Spec rowSpec = GridLayout.spec(ROW_INDEX, ROW_SPAN, ROW_WEIGHT);
    GridLayout.Spec columnSpec = GridLayout.spec(COLUMN_INDEX, COLUMN_SPAN, COLUMN_WIEGHT);
    GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec);
    
  5. android:layout_gravity="fill" 可以使單元格元素鋪滿寬高
  6. 指定layout_weight時最好一併指定layout_width=0

設計與實現

界面設計

界面構建: 使用GridLayout作爲每一節課的佈局,使用AppBarLayout實現向上滾動隱藏最上方周列表的效果。因爲要實現添加課程的功能,因此採用動態添加每一節課的子控件方式,需要使用LayoutParameters設置控件的屬性。
佈局文件: 頂部使用AppBar顯示周信息,下面使用GridLayout顯示課表,每一節課使用CardView+TextView顯示,空的小節需要使用一個空View設置,通過控制columnWeight使控件均分剩餘空間。

  • CoordinatorLayout - 配合NestedScrollViewAppBarLayout實現上滑隱藏頂部效果
    • FloatingActionBuuton - 左下角的設置按鈕
    • NestedScrollView - 設置app:layout_behavior="@string/appbar_scrolling_view_behavior">
      • GridLayout - 課表,每一小節爲一行,使用rowSpan設置多個小節的課
    • AppBarLayout
      • LinearLayout - 頂部第幾周,設置app:layout_scrollFlags="scroll|enterAlways"

實現與代碼

類ClassInfo爲數據對象,其它的學期、第一週開始日期爲靜態變量。啓動時獲取數據,然後遍歷所有課程,創建CardView或空View添加到GridLayout中。

佈局文件代碼

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/main_float_btn_setting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
        app:backgroundTint="@color/colorAccent"
        android:clickable="true"
        android:focusable="true"
        android:src="@drawable/sqsetting"/>
    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <GridLayout
            android:id="@+id/main_grid_layout_all_class"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:columnCount="6"
            android:orientation="vertical"
            android:useDefaultMargins="true"
            android:background="@color/whiteSmoke"
            android:rowCount="13">
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass1" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass2" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass3" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass4" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass5" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass6" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass7" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass8" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass9" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass10" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass11" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass12" />
            <TextView
                style="@style/TextViewTimeTable"
                android:text="@string/timeClass13" />
        </GridLayout>
    </androidx.core.widget.NestedScrollView>
    <com.google.android.material.appbar.AppBarLayout
        app:elevation="0dp"
        android:background="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            app:layout_scrollFlags="scroll|enterAlways"
            android:paddingStart="4dp"
            android:paddingEnd="4dp"
            android:paddingTop="3dp"
            android:paddingBottom="3dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <LinearLayout
                android:layout_width="45dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="center"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/main_text_view_week_index"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="第一週"
                    android:textStyle="bold"
                    android:textColor="@color/whiteSmoke"
                    android:textSize="14sp" />
                <TextView
                    android:id="@+id/main_text_view_semester"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="研一上"
                    android:textColor="@color/whiteSmoke"
                    android:textSize="13sp" />
            </LinearLayout>
            <LinearLayout
                android:id="@+id/main_linear_weekdays"
                android:orientation="horizontal"
                android:layout_gravity="center"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <TextView
                    style="@style/TextViewWeekDay"
                    android:text="@string/monday" />
                <TextView
                    style="@style/TextViewWeekDay"
                    android:text="@string/tuesday" />
                <TextView
                    style="@style/TextViewWeekDay"
                    android:text="@string/wednesday" />
                <TextView
                    style="@style/TextViewWeekDay"
                    android:text="@string/thursday" />
                <TextView
                    style="@style/TextViewWeekDay"
                    android:text="@string/friday" />
            </LinearLayout>
        </LinearLayout>
    </com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

MainActivity代碼

package com.example.lessonschedule;
// import .......
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private static ArrayList<ClassInfo> allClasses;
    private static int semesterIndex, grade;
    private static String teachWeekStartDate;
    private GridLayout allClassGrid;
    private TextView semesterTextView, weekIndexTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        allClassGrid = findViewById(R.id.main_grid_layout_all_class);
        semesterTextView = findViewById(R.id.main_text_view_semester);
        weekIndexTextView = findViewById(R.id.main_text_view_week_index);
        findViewById(R.id.main_float_btn_setting).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SettingActivity.class));
            }
        });
        if (allClasses == null) getStoredData();
        addAllClass();
        setSemesterWeek();
    }
    private void setSemesterWeek() {
        final String[] num2ChineseNum = new String[]{null, "一", "二", "三"};
        if (grade > 0 && grade <= 3 && semesterIndex >= 0 && semesterIndex <= 1)
            semesterTextView.setText(String.format("研%s%s", num2ChineseNum[grade], (semesterIndex == 0 ? "上" : "下")));
        int dayOfWeek = GregorianCalendar.getInstance().get(Calendar.DAY_OF_WEEK);
        // 週日是第一天
        if (dayOfWeek >= 2 && dayOfWeek <= 6) {
            LinearLayout linearLayout = findViewById(R.id.main_linear_weekdays);
            linearLayout.getChildAt(dayOfWeek - 2).setBackgroundColor(getResources().getColor(R.color.colorAccentDark));
        }
        try {
            Date now = new Date();
            Date start = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).parse(teachWeekStartDate);
            if (start != null) {
                Log.e(TAG, "setSemesterWeek: " + start + ", now: " + now + " dif: " + (now.getTime() - start.getTime()));
                long weekIndex = (now.getTime() - start.getTime()) / (7L * 24 * 60 * 60 * 1000) + 1;
                weekIndexTextView.setText(String.format(Locale.CHINA, "第%d周", weekIndex));
            }
        } catch (Exception e) {
            Log.e(TAG, "getStoredData: incorrect date format");
        }
    }
    private void addAllClass() {
        int k = 0;
        for (int i = 1; i <= 5; i++) {
            for (int j = 1; j <= 13; ) {
                if (k < allClasses.size() && allClasses.get(k).getWeekdayIndex() == i && allClasses.get(k).getClassStartIndex() == j) {
                    ClassInfo tmpClassInfo = allClasses.get(k);
                    TextView textView = new TextView(new ContextThemeWrapper(this, R.style.TextViewClassInfo));
                    textView.setText(tmpClassInfo.toClassInfoString());
                    textView.setLayoutParams(new FrameLayout.LayoutParams(
                            FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
                    CardView cardView = new CardView(new ContextThemeWrapper(this, R.style.CardViewClass));
                    cardView.addView(textView);
                    cardView.setCardBackgroundColor(tmpClassInfo.getBgColor());
                    GridLayout.Spec rowSpec = GridLayout.spec(j - 1, tmpClassInfo.getClassPeriod());
                    GridLayout.Spec columnSpec = GridLayout.spec(i, 1.0f);
                    GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec);
                    layoutParams.setGravity(Gravity.FILL);
                    layoutParams.height = 0;
                    layoutParams.width = 0;
                    allClassGrid.addView(cardView, layoutParams);
                    j += tmpClassInfo.getClassPeriod();
                    k++;
                } else {
                    View child = new TextView(this);
                    GridLayout.Spec rowSpec = GridLayout.spec(j - 1, 1);
                    GridLayout.Spec columnSpec = GridLayout.spec(i, 1, 1.0f);
                    GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec);
                    layoutParams.width = 0;
                    layoutParams.height = GridLayout.LayoutParams.WRAP_CONTENT;
                    layoutParams.setGravity(Gravity.FILL);
                    child.setBackgroundColor(getResources().getColor(R.color.lightGray, null));
                    allClassGrid.addView(child, layoutParams);
                    j += 1;
                }
            }
        }
    }
    private void getStoredData() {
        allClasses = new ArrayList<>();
        allClasses.add(new ClassInfo("計算數論", "3A112", 1, 15, 1, 6, 2));
        allClasses.add(new ClassInfo("信號與信息處理", "3C101", 2, 16, 1, 11, 3));
        allClasses.add(new ClassInfo("高級計算機網絡", "3B202", 1, 16, 2, 3, 3));
        allClasses.add(new ClassInfo("高級計算機網絡", "3C101", 8, 12, 4, 1, 2));
        allClasses.add(new ClassInfo("計算數論", "3A112", 1, 14, 4, 6, 2));
        semesterIndex = 1;
        grade = 1;
        teachWeekStartDate = "2020-02-17";
    }
}

ClassInfo類代碼

package com.example.lessonschedule;
// import ...
public class ClassInfo {
    
    static enum ColorSupported {
        Red, Pink, Purple, Deep_Purple, Indigo, Blue, Light_Blue, Cyan, Teal, Green, Light_Green, Lime, Amber, Orange
    }

    static final Map<ColorSupported, Integer> colorEnum2Color = new HashMap<ColorSupported, Integer>() {{
        put(ColorSupported.Red, Color.rgb(244, 67, 54));
        put(ColorSupported.Pink, Color.rgb(233, 30, 99));
        put(ColorSupported.Purple, Color.rgb(156, 39, 176));
        put(ColorSupported.Deep_Purple, Color.rgb(103, 58, 183));
        put(ColorSupported.Indigo, Color.rgb(63, 81, 181));
        put(ColorSupported.Blue, Color.rgb(33, 150, 243));
        put(ColorSupported.Light_Blue, Color.rgb(3, 169, 244));
        put(ColorSupported.Cyan, Color.rgb(0, 188, 212));
        put(ColorSupported.Teal, Color.rgb(0, 150, 136));
        put(ColorSupported.Green, Color.rgb(76, 175, 80));
        put(ColorSupported.Light_Green, Color.rgb(139, 195, 74));
        put(ColorSupported.Lime, Color.rgb(205, 220, 57));
        put(ColorSupported.Amber, Color.rgb(255, 193, 7));
        put(ColorSupported.Orange, Color.rgb(255, 152, 0));
    }};

    private String name;
    private String location;
    private int weekdayIndex;
    private int startWeek, endWeek;
    private int classStartIndex, classPeriod;
    private Integer bgColor;
    ClassInfo(String name, String location, int startWeek, int endWeek, int weekdayIndex, int classStartIndex, int classPeriod) {
        this.name = name;
        this.location = location;
        this.startWeek = startWeek;
        this.endWeek = endWeek;
        this.classStartIndex = classStartIndex;
        this.classPeriod = classPeriod;
        this.weekdayIndex = weekdayIndex;
        this.bgColor = colorEnum2Color.get(ColorSupported.values()[(int) (Math.random() * ColorSupported.values().length)]);
    }
    ClassInfo(String name, String location, int startWeek, int endWeek, int weekdayIndex, int classStartIndex, int classPeriod, ColorSupported bgColor) {
        this(name, location, startWeek, endWeek, weekdayIndex, classStartIndex, classPeriod);
        this.bgColor = colorEnum2Color.get(bgColor);
    }
    String toClassInfoString() {
        return name + "\n" + location + "\n" + startWeek + "~" + endWeek + "周";
    }
    // getter and setter
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章