擼一個Android高性能日曆控制項,高仿魅族

来源:http://www.cnblogs.com/huanghaibin/archive/2017/03/24/6612854.html
-Advertisement-
Play Games

Android原生的CalendarView根本無法滿足我們日常開發的需要,在開發吾記APP的過程中,我覺得需要來一款高性能且美觀簡潔的日曆控制項,覺得魅族的日曆風格十分適合,於是打算擼一款。 github地址:https://github.com/huanghaibin-dev/CalendarVi ...


Android原生的CalendarView根本無法滿足我們日常開發的需要,在開發吾記APP的過程中,我覺得需要來一款高性能且美觀簡潔的日曆控制項,覺得魅族的日曆風格十分適合,於是打算擼一款。

github地址:https://github.com/huanghaibin-dev/CalendarView

 

compile 'com.haibin:calendarview:1.0.2'

 

先上效果圖:

 

動手之前我們需要分析一下魅族是怎麼設計如此高性能的日曆的,我們打開開發者選項中的顯示佈局邊界:

 

好吧,一開始我以為日曆界面是ViewPager+RecyclerView的,但是這麼一看明顯就不是了,如果是RecyclerView,那麼我們假設每個月的卡片都有5*7=35個item,每個item根佈局是RelativeLayout+3個TextView,我們大概估算一下日曆初始化時要載入的控制項:

3個ViewPager的item * 35個RecyclerView的Item * 4(每個item的控制項數) + 8 (星期欄)= 420+ 

我的天,這可不能這麼乾,明顯性能大打折扣,我們再來看看月份控制項:

好吧,這裡看上去就是ViewPager+RecyclerView來做的,每個RecyclerView的item都只是一個控制項,裡面繪製了文本 ,這裡大概就分析清楚了。

我們採取折中的方式,日曆界面和月份卡界面均採用ViewPager+RecyclerView的方式,不同的是所有的item我們都採用自定義ViewCanvas繪製的方式來做,這樣性能雖然比不上魅族,但速度體驗基本差不多,下麵先看日曆界面的item代碼:只需要繪製3個文本即可

 

public class CellView extends View {

    private int mDay = 20;
    private String mLunar;
    private String mScheme;
    private Paint mDayPaint = new Paint();
    private Paint mLunarPaint = new Paint();
    private Paint mSchemePaint = new Paint();
    private Paint mCirclePaint = new Paint();
    private int mRadius;
    private int mCirclePadding;
    private int mCircleColor;

    public CellView(Context context) {
        this(context, null);
    }

    public CellView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mDayPaint.setAntiAlias(true);
        mDayPaint.setColor(Color.BLACK);
        mDayPaint.setFakeBoldText(true);
        mDayPaint.setTextAlign(Paint.Align.CENTER);

        mLunarPaint.setAntiAlias(true);
        mLunarPaint.setColor(Color.GRAY);
        mLunarPaint.setTextAlign(Paint.Align.CENTER);

        mSchemePaint.setAntiAlias(true);
        mSchemePaint.setColor(Color.WHITE);
        mSchemePaint.setFakeBoldText(true);
        mSchemePaint.setTextAlign(Paint.Align.CENTER);

        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setStyle(Paint.Style.FILL);

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CellView);
        mDayPaint.setTextSize(array.getDimensionPixelSize(R.styleable.CellView_cell_day_text_size, 18));
        mLunarPaint.setTextSize(array.getDimensionPixelSize(R.styleable.CellView_cell_lunar_text_size, 12));
        mRadius = (int) array.getDimension(R.styleable.CellView_cell_scheme_radius, 8);
        mSchemePaint.setTextSize(array.getDimensionPixelSize(R.styleable.CellView_cell_scheme_text_size, 6));
        mCirclePadding = array.getDimensionPixelSize(R.styleable.CellView_cell_circle_padding, 4);
        mCirclePaint.setColor(array.getColor(R.styleable.CellView_cell_circle_color, 0xff16BB7F));
        array.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int w = (width - getPaddingLeft() - getPaddingRight());
        int h = (height - getPaddingTop() - getPaddingBottom()) / 4;
        canvas.drawText(String.valueOf(mDay), w / 2, 2 * h + getPaddingTop(), mDayPaint);
        canvas.drawText(mLunar, w / 2, 4 * h + getPaddingTop(), mLunarPaint);
        if (!TextUtils.isEmpty(mScheme)) {
            canvas.drawCircle(w / 2 + mCirclePadding + mDayPaint.getTextSize(), getPaddingTop() + h, mRadius, mCirclePaint);
            canvas.drawText(mScheme, w / 2 + mCirclePadding + mDayPaint.getTextSize(), getPaddingTop() + mRadius / 2 + h, mSchemePaint);
        }
    }

    /**
     * 初始化日曆
     * @param day 天
     * @param lunar 農曆
     * @param scheme 事件標記
     */
    void init(int day, String lunar, String scheme) {
        this.mDay = day;
        this.mLunar = lunar;
        this.mScheme = scheme;
    }

    void setTextColor(int textColor) {
        mDayPaint.setColor(textColor);
        mLunarPaint.setColor(textColor);
    }

    void setCircleColor(int circleColor) {
        mCirclePaint.setColor(circleColor);
        invalidate();
    }
}

月份卡自定義View

 

public class MonthView extends View {
    private int mDiff;//第一天偏離周日多少天
    private int mCount;//總數
    private int mLastCount;//最後一行的天數
    private int mLine;//多少行
    private Paint mPaint = new Paint();
    private Paint mSchemePaint = new Paint();
    private List<Calendar> mSchemes;
    private Calendar mCalendar;

    public MonthView(Context context) {
        this(context, null);
    }

    public MonthView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mSchemePaint.setAntiAlias(true);
        mSchemePaint.setTextAlign(Paint.Align.CENTER);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MonthView);
        mPaint.setTextSize(array.getDimensionPixelSize(R.styleable.MonthView_month_view_text_size, 12));
        mSchemePaint.setTextSize(array.getDimensionPixelSize(R.styleable.MonthView_month_view_text_size, 12));
        mPaint.setColor(array.getColor(R.styleable.MonthView_month_view_text_color, Color.BLACK));
        mSchemePaint.setColor(array.getColor(R.styleable.MonthView_month_view_remark_color, Color.RED));
        array.recycle();
        measureLine();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int pLeft = getPaddingLeft();
        int w = (width - getPaddingLeft() - getPaddingRight()) / 7;
        int h = (height - getPaddingTop() - getPaddingBottom()) / 6;
        int d = 0;
        for (int i = 0; i < mLine; i++) {
            if (i == 0) {//第一行
                for (int j = 0; j < (7 - mDiff); j++) {
                    ++d;
                    canvas.drawText(String.valueOf(j + 1), mDiff * w + j * w + pLeft + w / 2, h, isScheme(d) ? mSchemePaint : mPaint);
                }
            } else if (i == mLine - 1 && mLastCount != 0) {
                int first = mCount - mLastCount + 1;
                for (int j = 0; j < mLastCount; j++) {
                    ++d;
                    canvas.drawText(String.valueOf(first), j * w + pLeft + w / 2, (i + 1) * h, isScheme(d) ? mSchemePaint : mPaint);
                    ++first;
                }
            } else {
                int first = i * 7 - mDiff + 1;
                for (int j = 0; j < 7; j++) {
                    ++d;
                    canvas.drawText(String.valueOf(first), j * w + pLeft + w / 2, (i + 1) * h, isScheme(d) ? mSchemePaint : mPaint);
                    ++first;
                }
            }
        }
    }

    /**
     * 計算行數
     */
    private void measureLine() {
        int offset = mCount - (7 - mDiff);
        mLine = 1 + (offset % 7 == 0 ? 0 : 1) + offset / 7;
        mLastCount = offset % 7;
    }

    /**
     * 初始化月份卡
     * @param mDiff 偏離天數
     * @param mCount 當月總天數
     * @param mYear 哪一年
     * @param mMonth 哪一月
     */
     void init(int mDiff, int mCount, int mYear, int mMonth) {
        this.mDiff = mDiff;
        this.mCount = mCount;
        mCalendar = new Calendar();
        mCalendar.setYear(mYear);
        mCalendar.setMonth(mMonth);
        measureLine();
        invalidate();
    }

    void setSchemes(List<Calendar> mSchemes) {
        this.mSchemes = mSchemes;
    }

    void setSchemeColor(int schemeColor) {
        if (schemeColor != 0)
            mSchemePaint.setColor(schemeColor);
        if(schemeColor == 0xff30393E)
            mSchemePaint.setColor(Color.RED);
    }

    private boolean isScheme(int day) {
        if (mSchemes == null || mSchemes.size() == 0)
            return false;
        mCalendar.setDay(day);
        return mSchemes.contains(mCalendar);
    }
}

  

其它代碼沒有什麼難度,日曆演算法是github上找的,更多詳情請看倉庫地址:https://github.com/huanghaibin-dev/CalendarView

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UINavigationController 返回手勢與 leftBarButtonItem UINavigationController 自帶從屏幕左側邊緣向右滑動的返回手勢,可以通過這個手勢實現 pop,或者 pop 中途取消 pop 而停留在當前控制器(UIViewController)。如果 ...
  • 前言 因為 "實戰項目系列" 涉及到數據持久化,這邊就來補充一下。 如本文有錯或理解偏差歡迎聯繫我,會儘快改正更新! 如有什麼問題,也可直接通過郵箱 [email protected] 聯繫我。 demo鏈接: https://pan.baidu.com/s/1hsspiio 密碼: dk3h 數據持 ...
  • UICollectionView 適配 iPhone 7 Plus 需求:在屏幕上水平放置 5 張正方形圖片,每張圖片的寬度相等,無縫隙排列鋪滿一個屏幕寬度。 看似很簡單的需求。用 UICollectionView 實現的話,把 UICollectionView 的寬度設置為屏幕寬度;屏幕寬度除以 ...
  • 譯者註:由於本人水平有限,譯文中難免會出現概念模糊、晦澀難懂,如果實在沒心思看下去,請發揮你的學習能動性,到原文中自行翻譯,感謝!!!點 "這裡" ,直達英文各種長句的世界。 好了,既然你選擇繼續往下看,那就一起來學習吧!!! 譯者:Noddy 地址: "直流電路理論" 如果你喜歡本譯文,請到項目上 ...
  • 1.應用程式 Android會同一系列核心應用程式包一起發佈,該應用程式包包括email客戶端,SMS短消息程式,日曆,地圖,瀏覽器,聯繫人管理程式等。所有的應用程式都是使用JAVA語言編寫的。 2.應用程式框架 開發人員也可以完全訪問核心應用程式所使用的API框架。該應用程式的架構設計簡化了組件的 ...
  • 有這樣一個ListView,要求在屏幕底部有一個篩選排序的浮動框: 1、手指下拉隱藏,上滑顯示 ; 2、如果沒做任何操作,2S之後,要自動顯示; 3、滑動到最底部,始終顯示。 首先看其效果圖: 實現上述效果,其實現原理如下: 1、在屏幕頂部固定一個BottomView,XML佈局最好使用Relati ...
  • 有一個Button 按鈕,要想為該按鈕設置onClick事件和OnTouch事件 此時,我們現在分析一下,是onTouch先執行,還是onClick執行,接下來我從FrameWork 源碼去探尋一下整個事件的執行流程和原理: 我們知道 Button ,TextView 等基礎控制項的基類都是View, ...
  • 主要原理:是在主界面有兩個空間,一個是EditText,一個是ListView,ListView是放在EditText下麵的,然後自定義建立一個adapter適配器, 這個適配器要繼承Filterable這個介面,並實現這個介面的兩個方法,一個是過濾方法,一個是過濾後的方法,一般我們是在過濾方法裡面 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...