不只是換殼上市!EC-05 與 Gogoro S2 實測比較

EC-05 由台灣山葉(YAMAHA)和 Gogoro 聯手打造,動力系統採用與 Gogoro S2 相同的 G2 鋁合金水冷永磁同步馬達(S-Version),許多人不禁疑問 EC-05 和 Gogoro S2 到底有什麼差別?難道只是換了一個車殼?更別說 EC-05 的價格還比 Gogoro S2 貴了 2,820 元。為了比較其中的差別,《科技新報》試駕了 EC-05 和 2020 年式的 Gogoro S2,帶來兩台車的第一手觀察。

EC-05 配備嵌合式環型頭燈,能在夜間騎乘時避免光線發散,確保前方視野的明亮程度。Gogoro S2 同樣配備環型頭燈,不過形狀略有不同,和一般機車一樣完全外露。EC-05 的後照鏡為菱形設計,視野較為寬廣,Gogoro S2 的後照鏡是圓形,視野較狹窄,而且騎乘者容易被自己的身體擋住部分視線。

Gogoro S2 前方配備了多功能置物箱,可以擺放手機和手套等小型物品,也能做為飲料置杯架使用,EC-05 就沒有這項裝置。Gogoro S2 的前方置物箱內附 USB 充電插槽,EC-05 的 USB 插孔則位於後座的置物箱。EC-05 使用無線智慧鑰匙,靠近車輛後按壓鑰匙的灰色部分進行解鎖。Gogoro S2 則使用 iQ system 智慧鑰匙卡,只要輕觸 S 標誌就能解鎖。

EC-05(左)配備嵌合式環型頭燈,Gogoro S2 的頭燈則外露。

EC-05 後照鏡呈菱形,視野較寬廣。

Gogoro S2 的後照鏡為圓形。

Gogoro S2 配備多功能置物箱,並附有 USB 充電插槽。

EC-05 的 USB 充電插槽位於後置物箱內側。

EC-05 以無線智慧鑰匙解鎖。

試乘時騎行陽明山的山路,EC-05 的穩定度和避震能力優於 Gogoro S2,在路面凹凸不平時較不容易顛簸,雙載騎乘時的差距更加明顯。EC-05 的座墊將後座的後方略為墊高,讓座墊呈現「山」字型的結構,側邊則採切削設計,讓騎乘者的腿部更為舒適。Gogoro S2 的後座較為平滑,中間使用皮質材質並標有「S」字樣。雖然乍看之下差異不大,但實際騎乘時後座乘客的感受明顯不同。

EC-05 的乘客在機車減速和加速時都更容易維持穩定,比較不會有往後飛出去或往前貼到前方騎乘者身上的狀況,即使長時間乘坐也不會滑動。Gogoro S2 的後座把手較為平直,EC-05 的把手彎曲向上,對於手不夠長的乘客更容易抓握。相較於 Gogoro S2,EC-05 能給予後座乘客更安穩的乘坐體驗。

EC-05 的座墊呈現「山」字型的凸起,讓後座乘客乘坐時更穩定。

Gogoro S2 的座墊較為平滑。

EC-05 與 Gogoro S2 座墊比較。

EC-05 的後座把手彎曲向上,較容易抓握。

Gogoro S2 的後座把手平直,手沒那麼長的後座乘客較難抓握。

EC-05(右)與 Gogoro S2 的後座把手差異從車尾可以明顯看出。

由於 EC-05 與 Gogoro S2 採用相同的動力系統,因此性能上的數據相差無幾。EC-05 最大馬力為 10hp,安全極速達到時速 90 公里,靜止加速到時速 50 公里需要 3.9 秒。Gogoro S2 最大馬力為 10.18hp,安全極速達到時速 92 公里,靜止加速到時速 50 公里僅需 3.8 秒,各方面都是 Gogoro S2 稍勝。

相較於 Gogoro S2,EC-05 的儀表板略為調整傾斜角度並向前方移動,用以減少騎乘者所需的視線移動。EC-05 的腳踏板具有菱形的凸起幫助止滑,讓騎乘時腳的位置可以更穩定。Gogoro S2 的腳踏板則是完全平滑,並有著「gogoro」的字樣。

EC-05 在車及座墊高度上也有更動,車高為 1,180 mm 略高於 Gogoro S2 的 1,080 mm,座高則以 768 mm 略低於 Gogoro S2 的 780 mm。調整過後 EC-05 在龍頭和座墊間的空間增加,騎乘時較為舒適也不容易意外按壓到喇叭等按鍵。不過 EC-05 的龍頭相較 Gogoro S2 略為沉重,力氣較小的使用者操控時可能會稍微費力。

EC-05 的腳踏板具有止滑的菱形格紋。

Gogoro S2 的腳踏板較為平滑。

身高 176 公分的騎乘者與 EC-05 的比例。

身高 156 公分的騎乘者與 EC-05 的比例。

YAMAHA 的設計理念標榜「人機官能」,也就是在設計時結合機械結構和人體工學。這樣的概念讓 EC-05 看似與 Gogoro S2 僅有微幅的差異,但這些細節卻在騎乘時能發揮不成比例的效果。至於這樣的差別值不值得多花 2,820 元購買,那就要看個人是否在意這些細節帶來的感受。

EC-05 提供藍灰色、深黑色、深藍灰色和白銀色 4 種顏色讓消費者選擇,定價為台幣 99,800 元。Gogoro S2 僅有灰黑色一個款式,定價為台幣 96,980 元。

(合作媒體:。圖片來源:)

延伸閱讀:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※高價收購3C產品,價格不怕你比較

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

【集合系列】- 深入淺出的分析IdentityHashMap

一、摘要

在集合系列的第一章,咱們了解到,Map 的實現類有 HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

應該有很多人不知道 IdentityHashMap 的存在,其中不乏工作很多年的 Java 開發者,本文主要從數據結構和算法層面,探討 IdentityHashMap 的實現。

二、簡介

IdentityHashMap 的數據結構很簡單,底層實際就是一個 Object 數組,但是在存儲上並沒有使用鏈表來存儲,而是將 K 和 V 都存放在 Object 數組上。

當添加元素的時候,會根據 Key 計算得到散列位置,如果發現該位置上已經有改元素,直接進行新值替換;如果沒有,直接進行存放。當元素個數達到一定閾值時,Object 數組會自動進行擴容處理。

打開 IdentityHashMap 的源碼,可以看到 IdentityHashMap 繼承了AbstractMap 抽象類,實現了Map接口、可序列化接口、可克隆接口。

public class IdentityHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, java.io.Serializable, Cloneable
{
    /**默認容量大小*/
    private static final int DEFAULT_CAPACITY = 32;
    
    /**最小容量*/
    private static final int MINIMUM_CAPACITY = 4;
    
    /**最大容量*/
    private static final int MAXIMUM_CAPACITY = 1 << 29;
    
    /**用於存儲實際元素的表*/
    transient Object[] table;
    
    /**數組大小*/
    int size;

    /**對Map進行結構性修改的次數*/
    transient int modCount;

    /**key為null所對應的值*/
    static final Object NULL_KEY = new Object();
    
    ......
}

可以看到類的底層,使用了一個 Object 數組來存放元素;在對象初始化時,IdentityHashMap 容量大小為64

public IdentityHashMap() {
        //調用初始化方法
        init(DEFAULT_CAPACITY);
}
private void init(int initCapacity) {
        //數組大小默認為初始化容量的2倍
        table = new Object[2 * initCapacity];
}

三、常用方法介紹

3.1、put方法

put 方法是將指定的 key, value 對添加到 map 里。該方法首先會對map做一次查找,通過==判斷是否存在key,如果有,則將舊value返回,將新value覆蓋舊value;如果沒有,直接插入,數組長度+1,返回null

源碼如下:

public V put(K key, V value) {
        //判斷key是否為空,如果為空,初始化一個Object為key
        final Object k = maskNull(key);

        retryAfterResize: for (;;) {
            final Object[] tab = table;
            final int len = tab.length;
            //通過key、length獲取數組小編
            int i = hash(k, len);
            
            //循環遍歷是否存在指定的key
            for (Object item; (item = tab[i]) != null;
                 i = nextKeyIndex(i, len)) {
                 //通過==判斷,是否數組中是否存在key
                if (item == k) {
                        V oldValue = (V) tab[i + 1];
                        //新value覆蓋舊value
                    tab[i + 1] = value;
                    //返回舊value
                    return oldValue;
                }
            }
            
            //數組長度 +1
            final int s = size + 1;
            //判斷是否需要擴容
            if (s + (s << 1) > len && resize(len))
                continue retryAfterResize;

            //更新修改次數
            modCount++;
            //將k加入數組
            tab[i] = k;
            //將value加入數組
            tab[i + 1] = value;
            size = s;
            return null;
        }
}

maskNull 函數,判斷 key 是否為空

private static Object maskNull(Object key) {
        return (key == null ? NULL_KEY : key);
}

hash 函數,通過 key 獲取 hash 值,結合數組長度通過位運算獲取數組散列下標

private static int hash(Object x, int length) {
        int h = System.identityHashCode(x);
        // Multiply by -127, and left-shift to use least bit as part of hash
        return ((h << 1) - (h << 8)) & (length - 1);
}

nextKeyIndex 函數,通過 hash 函數計算得到的數組散列下標,進行加2;因為一個 key、value 都存放在數組中,所以一個 map 對象佔用兩個數組下標,所以加2。

private static int nextKeyIndex(int i, int len) {
        return (i + 2 < len ? i + 2 : 0);
}

resize 函數,通過數組長度,進行擴容處理,擴容之後的長度為當前長度的2倍

private boolean resize(int newCapacity) {
        //擴容后的數組長度,為當前數組長度的2倍
        int newLength = newCapacity * 2;

        Object[] oldTable = table;
        int oldLength = oldTable.length;
        if (oldLength == 2 * MAXIMUM_CAPACITY) { // can't expand any further
            if (size == MAXIMUM_CAPACITY - 1)
                throw new IllegalStateException("Capacity exhausted.");
            return false;
        }
        if (oldLength >= newLength)
            return false;

        Object[] newTable = new Object[newLength];
        //將舊數組內容轉移到新數組
        for (int j = 0; j < oldLength; j += 2) {
            Object key = oldTable[j];
            if (key != null) {
                Object value = oldTable[j+1];
                oldTable[j] = null;
                oldTable[j+1] = null;
                int i = hash(key, newLength);
                while (newTable[i] != null)
                    i = nextKeyIndex(i, newLength);
                newTable[i] = key;
                newTable[i + 1] = value;
            }
        }
        table = newTable;
        return true;
}

3.2、get方法

get 方法根據指定的 key 值返回對應的 value。同樣的,該方法會循環遍曆數組,通過==判斷是否存在key,如果有,直接返回value,因為 key、value 是相鄰的存儲在數組中,所以直接在當前數組下標+1,即可獲取 value;如果沒有找到,直接返回null

值得注意的地方是,在循環遍歷中,是通過==判斷當前元素是否與key相同,如果相同,則返回value。咱們都知道,在 java 中,==對於對象類型參數,判斷的是引用地址,確切的說,是堆內存地址,所以,這裏判斷的是key的引用地址是否相同,如果相同,則返回對應的 value;如果不相同,則返回null

源碼如下:

public V get(Object key) {
        Object k = maskNull(key);
        Object[] tab = table;
        int len = tab.length;
        int i = hash(k, len);
        
        //循環遍曆數組,直到找到key或者,數組為空為值
        while (true) {
            Object item = tab[i];
            //通過==判斷,當前數組元素與key相同
            if (item == k)
                return (V) tab[i + 1];
            //數組為空
            if (item == null)
                return null;
            i = nextKeyIndex(i, len);
        }
}

3.3、remove方法

remove 的作用是通過 key 刪除對應的元素。該方法會循環遍曆數組,通過==判斷是否存在key,如果有,直接將keyvalue設置為null,對數組進行重新排列,返回舊 value。

源碼如下:

public V remove(Object key) {
        Object k = maskNull(key);
        Object[] tab = table;
        int len = tab.length;
        int i = hash(k, len);

        while (true) {
            Object item = tab[i];
            if (item == k) {
                modCount++;
                //數組長度減1
                size--;
                    V oldValue = (V) tab[i + 1];
                //將key、value設置為null
                tab[i + 1] = null;
                tab[i] = null;
                //刪除該元素后,需要把原來有衝突往後移的元素移到前面來
                closeDeletion(i);
                return oldValue;
            }
            if (item == null)
                return null;
            i = nextKeyIndex(i, len);
        }
}

closeDeletion 函數,刪除該元素后,需要把原來有衝突往後移的元素移到前面來,對數組進行重寫排列;

private void closeDeletion(int d) {
        // Adapted from Knuth Section 6.4 Algorithm R
        Object[] tab = table;
        int len = tab.length;

        Object item;
        for (int i = nextKeyIndex(d, len); (item = tab[i]) != null;
             i = nextKeyIndex(i, len) ) {
            int r = hash(item, len);
            if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) {
                tab[d] = item;
                tab[d + 1] = tab[i + 1];
                tab[i] = null;
                tab[i + 1] = null;
                d = i;
            }
        }
}

四、總結

  1. IdentityHashMap 的實現不同於HashMap,雖然也是數組,不過IdentityHashMap中沒有用到鏈表,解決衝突的方式是計算下一個有效索引,並且將數據keyvalue緊挨着存在map中,即table[i]=keytable[i+1]=value

  2. IdentityHashMap 允許keyvalue都為null,當keynull的時候,默認會初始化一個Object對象作為key

  3. IdentityHashMap在保存、刪除、查詢數據的時候,以key為索引,通過==來判斷數組中元素是否與key相同,本質判斷的是對象的引用地址,如果引用地址相同,那麼在插入的時候,會將value值進行替換;

IdentityHashMap 測試例子:

public static void main(String[] args) {
        Map<String, String> identityMaps = new IdentityHashMap<String, String>();

        identityMaps.put(new String("aa"), "aa");
        identityMaps.put(new String("aa"), "bb");
        identityMaps.put(new String("aa"), "cc");
        identityMaps.put(new String("aa"), "cc");
        //輸出添加的元素
        System.out.println("數組長度:"+identityMaps.size() + ",輸出結果:" + identityMaps);
    }

輸出結果:

數組長度:4,輸出結果:{aa=aa, aa=cc, aa=bb, aa=cc}

儘管key的內容是一樣的,但是key的堆地址都不一樣,所以在插入的時候,插入了4條記錄。

五、參考

1、JDK1.7&JDK1.8 源碼

2、

3、

作者:炸雞可樂
出處:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

小白理解安卓虛擬機以及華為的’諾亞方舟’

虛擬機提到虛擬機,大家可能第一反應就是java中好像有虛擬機這個玩意。但是安卓中的虛擬機是什麼呢?是和java一樣的嗎?那麼我們先來了解一下java中的JVM!

JVM,搞java的肯定對它了解不少。JVM本質上就是一個軟件,是計算機硬件的一層軟件抽象,在這之上才幹夠運行Java程序,JAVA在編譯後會生成相似於彙編語言的JVM字節碼,與C語言編譯后產生的彙編語言不同的是,C編譯成的彙編語言會直接在硬件上跑。但JAVA編譯後生成的字節碼是在JVM上跑,須要由JVM把字節碼翻譯成機器指令。才幹使JAVA程序跑起來。JVM運行在操作系統上,屏蔽了底層實現的差異。從而有了JAVA吹噓的平台獨立性和Write Once Run Anywhere。依據JVM規範實現的詳細虛擬機有幾十種,主流的JVM包括Hotspot、Jikes RVM等。都是用C/C++和彙編編寫的,每一個JRE編譯的時候針對每一個平台編譯。因此下載JRE(JVM、Java核心類庫和支持文件)的時候是分平台的,JVM的作用是把平台無關的.class裏面的字節碼翻譯成平台相關的機器碼,來實現跨平台。

說白了,簡單點,就是:

                                                                      Java

                                                                      .java文件 -> .class文件 -> .jar文件

最後執行是class文件,有的會被再次打包成jar文件。

了解了這些之後,我們再去了解Android 中的虛擬機。

一、Dalvik虛擬機

Dalvik虛擬機( Dalvik Virtual Machine ),簡稱Dalvik VM或者DVM。這就是Android中的虛擬機。最初它的產生,是因為Google為了解決與Oracle之間關於Java相關專利和授權的糾紛,開發了DVM。

Android既然存在虛擬機,肯定也是在這個DVM上執行的。它的執行流程和JVM很像:

                                                                       Android

                                                                      .java文件 –> .class文件 -> .dex文件->.apk

DVM執行的是.dex格式文件,JVM執行的是.class文件,android程序編譯完之後生產.class文件,然後,dex工具會把.class文件處理成.dex文件,然後把資源文件和.dex文件等打包成.apk文件,apk就是android package的意思。

除了上面所說的,專利授權的原因除外,其實還有因為如下原因:

    dvm是基於寄存器的虛擬機,而jvm是基於虛擬棧的虛擬機。寄存器存取速度比棧快得多,dvm可以根據硬件實現最大的優化,比較適合移動設備。

    class文件存在很多的冗餘信息,dex工具會去除冗餘信息,並把所有的.class文件整合到.dex文件中,減少了I/O操作,提高了類的查找速度。

不光是上面這些差異,還有運行環境。  

   Dalvik : 一個應用啟動都運行一個單獨的虛擬機運行在一個單獨的進程中

   JVM: 只能運行一個實例, 也就是所有應用都運行在同一個JVM中

 

 這個是早先的安卓虛擬機,運行速度還是相當慢的。基於寄存器的虛擬機允許更快的執行時間,但代價是編譯后的程序更大。於是新的Dex字節碼格式odex產生了。它的作用等同於dex,只不過是dex優化后的格式。在App安裝的過程中,會通過Socket向/system/bin/install進程發送dex_opt的指令,對Dex文件進行優化。在DexClassLoader動態加載Dex文件時,也會進行Dex的優化,形成odex文件。

 

為了適應硬件速度的提升,隨後在Android 2.2的DVM中加入了JIT 編譯器(Just-In-Time Compiler)。Dalvik 使用 JIT 進行即時編譯,藉助 Java HotSpot VM,JIT 編譯器可以對執行次數頻繁的 dex/odex 代碼進行編譯與優化,將 dex/odex 中的 Dalvik Code(Smali 指令集)翻譯成相當精簡的 Native Code 去執行,JIT 的引入使得 Dalvik 的性能提升了 3~6 倍。

JIT編譯器的引入,提升了安裝速度,減少了佔用的空間,但隨之帶來的問題就是:多個dex加載會非常慢;JIT中的解釋器解釋的字節碼會帶來CPU和時間的消耗;還有熱點代碼的Monitor一直在運行帶來電量的損耗。

 

 這種情況下,手機動不動就卡是難以避免的。相信各位如果那時候用着Android手機,一定印象非常深刻。因為並不是那麼好用。

這樣的狀況一直持續到Andorid 4.4,帶來了全新的虛擬機運行環境 ART(Android RunTime)的預覽版和全新的編譯策略 AOT(Ahead-of-time)。但那時候。 ART 是和 Dalvik 共存的,用戶可以在兩者之間進行選擇(感覺很奇怪,作為一個愛好者,我當時看到這個東西可以切換都是不曉得是什麼玩意,用戶可都是小白啊,沒有必要共存的吧)。在Android 5.0的時候,ART 全面取代 Dalvik 成為 Android 虛擬機運行環境,至此。Dalvik 退出歷史舞台,AOT 也成為唯一的編譯模式。

二、ART 

AOT 和 JIT 的不同之處在於:JIT 是在運行時進行編譯,是動態編譯,並且每次運行程序的時候都需要對 odex 重新進行編譯;而 AOT 是靜態編譯,應用在安裝的時候會啟動 dex2oat 通過靜態編譯的方式,來將所有的dex文件(包括Multidex)編譯oat文件,編譯完后的oat其實是一個標準的ELF文件,只是相對於普通的ELF文件多加了oat data section以及oat exec section這兩個段而已。(這兩個段裏面主要保存了兩種信息:Dex的文件信息以及類信息和Dex文件編譯之後的機器碼)。預編譯成 ELF 文件,每次運行程序的時候不用重新編譯,是真正意義上的本地應用。運行的文件格式也從odex轉換成了oat格式。

 

其實在Android5.0的時候我們能夠明顯感覺手機好用很多就是因為這個原因,從根本上換掉了那種存在着無法解決弊端的虛擬機。在 Android 5.x 和 6.x 的機器上,系統每次 OTA 升級完成重啟的時候都會有個應用優化的過程,這個過程就是剛才所說的 dex2oat 過程,這個過程比較耗時並且會佔用額外的存儲空間。

AOT 模式的預編譯解決了應用啟動和運行速度和耗資源(電等)問題的同時也帶來了另外兩個問題:

      1、應用安裝和系統升級之後的應用優化比較耗時,並且會更耗時間。因為系統和apk都是越來越大的。

      2、優化后的文件會佔用額外的存儲空間

在經過了兩個Android大版本的穩定后,在Android7.0又再次迎來了JIT的 回歸。

JIT的回歸,可不是把AOT模式給取代了,而是形成 了AOT/JIT 混合編譯模式,這種模式至今仍在使用

應用在安裝的時候 dex 不會被編譯。

應用在運行時 dex 文件先通過解析器(Interpreter)後會被直接執行(這一步驟跟 Android 2.2 – Android 4.4之前的行為一致),與此同時,熱點函數(Hot Code)會被識別並被 JIT 編譯后存儲在 jit code cache 中並生成 profile 文件以記錄熱點函數的信息。

手機進入 IDLE(空閑) 或者 Charging(充電) 狀態的時候,系統會掃描 App 目錄下的 profile 文件並執行 AOT 過程進行編譯。

(Profile文件會在JIT運行的過程中生成:每個APP都會有自己的Profile文件,保存在App本身的Local Storage中。Profile會保存所調用的類以及函數的Index,通過profman工具進行分析生成)

 

 

個人理解:哪種模式擅長干什麼就讓他去干什麼。

混合編譯模式綜合了 AOT 和 JIT 的各種優點,使得應用在安裝速度加快的同時,運行速度、存儲空間和耗電量等指標都得到了優化。

之前一直在說流暢,真的流暢在Android7.0上才感受到了些許。Android7.0系統也被用了相當長的一段時間。之後的Android8.0和Android9.0都是對各方面的優化,例如編譯文件、編譯器、GC。。

其中,值得一提的是華為的方舟編譯器。

  • 首先會判斷該設備支不支持方舟編譯器,如果支持,則從應用商店下發方舟版本的包
  • 方舟編譯器會把dex文件通過自己的IR翻譯方舟格式的機器碼,據資料說也是一個ELF文件,但是會增加一些段,猜測是Dex中類信息相關的段
  • 通過這種方式,來消除Java與JNI之間的通信的損耗,以及提升運行時的效率
  • 在方舟內部,還重新完善了GC算法,使得GC的頻率大大降低,減少應用卡頓的現象
  • 目前方舟只支持64位的So,並且對於加殼的So會出現一些問題。

方舟編譯器適配的應用,下載手機上都是方舟版本的包,特製的包用方舟編譯器編譯效率大大提升,之後直接執行就可以了,直接略過了在ART虛擬機上預編譯的過程。這樣的結果是很完美的,但是卻也沒辦法跳過一個弊端。那就是生態。還是不管是安卓還是iOS,這麼多年的時間沉澱中,他們的生態系統早就達到了一個非常完善的地步。安卓和iOS應用已經多達上千萬,而方舟適配應用的數量還非常有限。

谷歌宣布將停止對華為提供安卓系統更新之後,華為曝光了自主研發的鴻蒙操作系統。當時網友各種力挺。不過後來,華為董事長梁華在談及鴻蒙系統時稱,鴻蒙系統是為物聯網開發的,用於自動駕駛、遠程醫療等低時延場景。鴻蒙系統是不是兩手準備我們不得而知。但是,一個操作系統最重要的就是它的生態環境。縱觀華為現在的整個格局,目的非常明確,用方舟編譯器來擴大自己的用戶群體。當用戶的基數足夠龐大時,可以隨時隨地建立一個完善的生態系統。如果在未來某一天,Android全面限制華為的使用之後,在這危機關頭鴻蒙系統還是很有可能扛起國產手機的一面大旗。哪怕不是鴻蒙,我們也需要這樣一個生態不是嗎?

最初,突然去了解Android中的虛擬機,一個是想要明白到底Android中的虛擬機和JVM是不是一回事,還有就是想要明白華為發布方舟編譯器到底快到了哪裡。

上述相關資料均來自網絡,侵權必刪。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※公開收購3c價格,不怕被賤賣!

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

canvas入門,就是這個feel!

鈣素

Canvas 是在HTML5中新增的標籤用於在網頁實時生成圖像,並且可以操作圖像內容,基本上它是一個可以用JavaScript操作的位圖。也就是說我們將通過JS完成畫圖而不是css

canvas 默認布局為 inline-block,可以認為是一種特殊的圖片。

走起 ~

canvas 劃線

<canvas id="can" width="800" height="800"></canvas>

(寬高不能放在style裏面,否則比例不對)

canvas裏面的widthheight相當於圖片的原始尺寸,加了外部style的寬高,就相當於對圖片進行壓縮和拉伸。

// 1、獲取原生dom對象
let dom = document.getElementById('can');

// 2、獲取繪圖對象
let can = dom.getContext('2d'); // 3d是webgl

// 定義線條起點
can.moveTo(0,0);

// 定義線條中點(非終點)
can.lineTo(400,400);
can.lineTo(800,0);

// 對標記範圍進行描邊
can.stroke()

// 對標記範圍進行填充
can.fill();

設置線條屬性

線條默認寬度是 1

(一定要在繪圖之前設置。)

can.lineWidth = 2; //設置線條寬度
can.strokeStyle = '#f00';  // 設置線條顏色

can.fillStyle = '#f00';  // 設置填充區域顏色

折線樣式

  • miter:尖角(當尖角長度值過長時會自動變成折角,如果強制显示尖角:can.miterLimit = 100 設置尖角長度閾值。
  • round:圓角
  • bevel:折角
can.lineJoin = 'miter';
can.moveTo(100, 100);
can.lineTo(300, 100);
can.lineTo(100, 200);
can.stroke()

can.lineJoin = 'round';
can.moveTo(400, 100);
can.lineTo(600, 100);
can.lineTo(400, 200);
can.stroke()

can.lineJoin = 'bevel';
can.moveTo(700, 100);
can.lineTo(900, 100);
can.lineTo(700, 200);
can.stroke()

設置線帽

  • round:加圓角線帽
  • square:加直角線帽
  • butt:不加線帽
    can.lineCap = 'round';
    can.moveTo(100, 100);
    can.lineTo(300, 100);
    can.stroke()
    
     // 新建繪圖,使得上一次的繪畫樣式不會影響下面的繪畫樣式(代碼加在上一次繪畫和下一次繪畫中間。)
    can.beginPath()
    
    can.lineCap = 'square';
    can.moveTo(100, 200);
    can.lineTo(300, 200);
    can.stroke()
    
    can.beginPath()
    
    can.lineCap = 'butt';
    can.moveTo(100, 300);
    can.lineTo(300, 300);
    can.stroke()

畫矩形

// 參數:x,y,寬,高

can.rect(100,100,100,100);
can.stroke();

// 畫完即填充
can.fillRect(100,100,100,100);

畫圓弧

// 參數:圓心x,圓心y,半徑,圓弧起點與圓心的夾角度數,圓弧終點與圓心的夾角度數,true(逆時針繪畫)

can.arc(500,300,200,0,2*Math.PI/360*90,false);
can.stroke()

示例:

can.moveTo(500,300);
can.lineTo(500 + Math.sqrt(100), 300 + Math.sqrt(100))
can.arc(500, 300, 100, 2 * Math.PI / 360 *startDeg, 2 * Math.PI / 360 *endDeg, false);
can.closePath()//將圖形起點和終點用線連接起來使之成為封閉的圖形
can.fill()

Tips:

1、can.beginPath() // 新建繪圖,使得上一次的繪畫樣式不會影響下面的繪畫樣式(代碼加在上一次繪畫和下一次繪畫中間。)

2、can.closePath() //將圖形起點和終點用線連接起來使之成為封閉的圖形。

旋轉畫布

can.rotate(2*Math.PI/360*45); // 一定要寫在開始繪圖之前
can.fillRect(0,0,200, 10);

旋轉整個畫布的坐標系(參考坐標為畫布的(0,0)位置)

縮放畫布

can.scale(0.5,2);
can.fillRect(0,0,200, 10);

示例:

整個畫布:x方向縮放為原來的0.5,y方向拉伸為原來的2倍。

畫布位移

can.translate(100,100)
can.fillRect(0,0,200, 10);

保存與恢復畫布狀態

can.save() // 存檔:保存當前畫布坐標系狀態
can.restore() // 讀檔:恢復之前保存的畫布坐標系狀態

需要正確坐標系繪圖的時候,再讀檔之前的正確坐標系。

can.restore() // 將當前的畫布坐標系狀態恢復成上一次保存時的狀態
can.fillRect(dom.width/2, dom.height/2, 300, 100)

指針時鐘(案例)

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>clock</title>
    <style type="text/css">
        #can {
            width: 1000px;
            height: 600px;
            background: linear-gradient(45deg, green, skyblue);
        }
    </style>
</head>

<body>
    <canvas id="can" width="2000" height="1200"></canvas>
</body>

<script type="text/javascript">
    let dom = document.getElementById('can');

    let can = dom.getContext('2d');

    // 把畫布的圓心移動到畫布的中心
    can.translate(dom.width / 2, dom.height / 2);
    // 保存當前的畫布坐標系
    can.save()


    run();



    function run() {
        setInterval(function() {
            clearCanvas();
            draw();
        }, 10);
    }

    // 繪圖
    function draw() {
        let time = new Date();
        let hour = time.getHours();
        let min = time.getMinutes();
        let sec = time.getSeconds();
        let minSec = time.getMilliseconds();

        drawPannl();
        drawHour(hour, min, sec);
        drawMin(min, sec);
        drawSec(sec, minSec);
        drawPoint();
    }

    // 最簡單的方法:由於canvas每當高度或寬度被重設時,畫布內容就會被清空
    function clearCanvas() {
        dom.height = dom.height;
        can.translate(dom.width / 2, dom.height / 2);
        can.save()
    }

    // 畫錶盤
    function drawPannl() {
        can.beginPath();

        can.restore()
        can.save()

        can.lineWidth = 10;
        can.strokeStyle = 'skyblue';
        can.arc(0, 0, 400, 0, 2 * Math.PI);
        can.stroke();

        for (let i = 0; i < 12; i++) {
            can.beginPath();
            can.lineWidth = 16;
            can.strokeStyle = 'greenyellow';

            can.rotate(2 * Math.PI / 12)

            can.moveTo(0, -395);
            can.lineTo(0, -340);
            can.stroke();
        }

        for (let i = 0; i < 60; i++) {
            can.beginPath();
            can.lineWidth = 10;
            can.strokeStyle = '#fff';

            can.rotate(2 * Math.PI / 60)

            can.moveTo(0, -395);
            can.lineTo(0, -370);
            can.stroke();
        }
    }

    // 畫時針
    function drawHour(h, m, s) {
        can.beginPath();

        can.restore()
        can.save()

        can.lineWidth = 24;
        can.strokeStyle = 'palevioletred';
        can.lineCap = 'round'
        can.rotate(2 * Math.PI / (12 * 60 * 60) * (h * 60 * 60 + m * 60 + s))
        can.moveTo(0, 0);
        can.lineTo(0, -200);
        can.stroke();
    }

    // 畫分針
    function drawMin(m, s) {
        can.beginPath();

        can.restore()
        can.save()

        can.lineWidth = 14;
        can.strokeStyle = '#09f';
        can.lineCap = 'round'
        can.rotate(2 * Math.PI / (60 * 60) * (m * 60 + s))
        can.moveTo(0, 0);
        can.lineTo(0, -260);
        can.stroke();
    }

    // 畫秒針
    function drawSec(s, ms) {
        can.beginPath();

        can.restore()
        can.save()

        can.lineWidth = 8;
        can.strokeStyle = '#f00';
        can.lineCap = 'round'
        can.rotate(2 * Math.PI / (60 * 1000) * (s * 1000 + ms));
        can.moveTo(0, 50);
        can.lineTo(0, -320);
        can.stroke();
    }


    // 畫中心點
    function drawPoint() {
        can.beginPath();

        can.restore()
        can.save()

        can.lineWidth = 10;
        can.fillStyle = 'red';
        can.arc(0, 0, 12, 0, 2 * Math.PI);
        can.fill();
    }
</script>

</html>

圓弧時鐘(案例)

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>clock</title>
    <style type="text/css">
        #can {
            width: 1000px;
            height: 600px;
            background: linear-gradient(45deg, rgb(94, 53, 6), black);
        }
    </style>
</head>

<body>
    <canvas id="can" width="2000" height="1200"></canvas>
</body>

<script type="text/javascript">
    let dom = document.getElementById('can');

    let can = dom.getContext('2d');

    // 把畫布的圓心移動到畫布的中心
    can.translate(dom.width / 2, dom.height / 2);
    // 保存當前的畫布坐標系
    can.save();

    // 圓形指針起始角度
    let startDeg = 2 * Math.PI / 360 * 270;


    run();
    // draw();


    function run() {
        setInterval(function() {
            clearCanvas();
            draw();
        }, 20);
    }

    // 繪圖
    function draw() {
        let time = new Date();
        // let hour = time.getHours();
        let hour = time.getHours() > 10 ? time.getHours() - 12 : time.getHours();
        let min = time.getMinutes();
        let sec = time.getSeconds();
        let minSec = time.getMilliseconds();

        drawPannl();
        drawTime(hour, min, sec, minSec);
        drawHour(hour, min, sec);
        drawMin(min, sec);
        drawSec(sec, minSec);
        drawPoint();
    }

    // 最簡單的方法:由於canvas每當高度或寬度被重設時,畫布內容就會被清空
    function clearCanvas() {
        dom.height = dom.height;
        can.translate(dom.width / 2, dom.height / 2);
        can.save()
    }

    // 畫錶盤
    function drawPannl() {

        can.restore()
        can.save()

        // 設置時錶盤
        can.beginPath();
        can.lineWidth = 50;
        can.strokeStyle = 'rgba(255,23,87,0.2)';
        can.arc(0, 0, 400, 0, 2 * Math.PI);
        can.stroke();
        // 設置分錶盤
        can.beginPath();
        can.strokeStyle = 'rgba(169,242,15,0.2)';
        can.arc(0, 0, 345, 0, 2 * Math.PI);
        can.stroke();
        // 設置秒錶盤
        can.beginPath();
        can.strokeStyle = 'rgba(21,202,230,0.2)';
        can.arc(0, 0, 290, 0, 2 * Math.PI);
        can.stroke();


        // 小時刻度
        // for (let i = 0; i < 12; i++) {
        //     can.beginPath();
        //     can.lineWidth = 16;
        //     can.strokeStyle = 'rgba(0,0,0,0.2)';

        //     can.rotate(2 * Math.PI / 12)

        //     can.moveTo(0, -375);
        //     can.lineTo(0, -425);
        //     can.stroke();
        // }

        // 分針刻度
        // for (let i = 0; i < 60; i++) {
        //     can.beginPath();
        //     can.lineWidth = 10;
        //     can.strokeStyle = '#fff';

        //     can.rotate(2 * Math.PI / 60)

        //     can.moveTo(0, -395);
        //     can.lineTo(0, -370);
        //     can.stroke();
        // }
    }

    // 畫時針
    function drawHour(h, m, s) {

        let rotateDeg = 2 * Math.PI / (12 * 60 * 60) * (h * 60 * 60 + m * 60 + s);

        can.beginPath();
        can.restore()
        can.save()

        // 時針圓弧
        can.lineWidth = 50;
        can.strokeStyle = 'rgb(255,23,87)';
        can.lineCap = 'round';
        can.shadowColor = "rgb(255,23,87)"; // 設置陰影顏色
        can.shadowBlur = 20; // 設置陰影範圍
        can.arc(0, 0, 400, startDeg, startDeg + rotateDeg);
        can.stroke();

        // 時針指針
        can.beginPath();
        can.lineWidth = 24;
        can.strokeStyle = 'rgb(255,23,87)';
        can.lineCap = 'round'
        can.rotate(rotateDeg)
        can.moveTo(0, 0);
        can.lineTo(0, -100);
        can.stroke();



    }

    // 畫分針
    function drawMin(m, s) {

        let rotateDeg = 2 * Math.PI / (60 * 60) * (m * 60 + s);

        can.beginPath();
        can.restore()
        can.save()

        // 分針圓弧
        can.lineWidth = 50;
        can.strokeStyle = 'rgb(169,242,15)';
        can.lineCap = 'round'
        can.shadowColor = "rgb(169,242,15)";
        can.shadowBlur = 20;
        can.arc(0, 0, 345, startDeg, startDeg + rotateDeg);
        can.stroke();

        // 分針指針
        can.beginPath();
        can.lineWidth = 14;
        can.strokeStyle = 'rgb(169,242,15)';
        can.lineCap = 'round'
        can.rotate(rotateDeg)
        can.moveTo(0, 0);
        can.lineTo(0, -160);
        can.stroke();
    }

    // 畫秒針
    function drawSec(s, ms) {

        let rotateDeg = 2 * Math.PI / (60 * 1000) * (s * 1000 + ms);

        can.beginPath();
        can.restore()
        can.save()

        can.lineWidth = 50;
        can.strokeStyle = 'rgb(21,202,230)';
        can.lineCap = 'round'
        can.arc(0, 0, 290, startDeg, startDeg + rotateDeg);
        can.stroke();

        can.beginPath();
        can.lineWidth = 8;
        can.strokeStyle = 'rgb(21,202,230)';
        can.lineCap = 'round'
        can.shadowColor = "rgb(21,202,230)";
        can.shadowBlur = 20;
        can.rotate(rotateDeg);
        can.moveTo(0, 50);
        can.lineTo(0, -220);
        can.stroke();
    }


    // 畫中心點
    function drawPoint() {
        can.beginPath();
        can.restore()
        can.save()

        can.lineWidth = 10;
        can.fillStyle = 'red';
        can.arc(0, 0, 12, 0, 2 * Math.PI);
        can.fill();
    }

    // 显示数字時鐘
    function drawTime(h, m, s, ms) {
        can.font = '60px Calibri';
        can.fillStyle = '#0f0'
        can.shadowColor = "#fff";
        can.shadowBlur = 20;
        can.fillText(`${h}:${m}:${s}.${ms}`, -140, -100);
    }
</script>

</html>

(啾咪 ^.<)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

VSCode, Django, and Anaconda開發環境集成配置[Windows]

  之前一直是在Ubuntu下進行Python和Django開發,最近換了電腦,把在Virtual Box 下跑的Ubuntu開發機挪過來總是頻繁崩潰,索性就嘗試把開發環境挪到Windows主力機了。

不得不說,巨硬家這幾年在多元並包方面真的是走在了世界前列。特別是VSCode,兩年前已經成為了我在Linux下的主力IDE。於是直接Google到了這篇爽文:Django Tutorial in Visual Studio Code, 下面會結合Anaconda的開發環境,翻譯這篇官方指導。

 

0x1 – 安裝清單

– Win10

– Anaconda3

– Vistual Studio Code(VSCode)

 分別下載並安裝好以上三個神器,選的都是最新穩定版。

 

0x2 – Anaconda 管理並配置Python開發環境

  • 打開Anaconda Prompt終端命令行工具(不是Anaconda Navigator),先來練習下conda,類似於pip和virtualenv的結合體。

  • 用conda創建Python開發虛擬環境,注意要在環境名稱(這裡是my_env)后加上python版本。
conda create -n wechat_env python=3.7
  • 移除環境

 conda remove -n wechat_env –all

  • 查看虛擬環境列表, *代表當前工作所在的虛擬環境:
(base) C:\Users\freman.zhang>conda env list
# conda environments:
#
base                  *  C:\Anaconda3
wechat_env               C:\Anaconda3\envs\wechat_env
  •  激活及切換環境:
(base) C:\Users\freman.zhang>conda activate wechat_env
(wechat_env) C:\Users\freman.zhang>conda env list
# conda environments:
#
base                     C:\Anaconda3
wechat_env            *  C:\Anaconda3\envs\wechat_env
(wechat_env) C:\Users\freman.zhang>conda deactivate
(base) C:\Users\freman.zhang>
  • 安裝Django到開發環境,如有需要可以指定版本號。
conda install django
conda install django==2.2.5

 

 

這樣conda就會自動下載並安裝好Python和Django到指定的開發環境,無需再事先或單獨安裝在OS中。

conda詳細的管理命令可以到中詳細了解。

 

0x3 – 在VSCode中配置集成開發環境

 Django是一個為安全,快速和可擴展的web開發所設計的高級Python框架。Django對於URL路由, 頁面模板和數據處理等提供豐富的支持。

在接下來的tutorial中,我們將創建一個簡單的三頁應用,並將會用到一個通用的基礎模板。通過在VSCode中完整的過一遍這個開發過程,我們將可以更好的理解如何使用VSCode的命令終端,編輯器和調試器等來高效便捷地進行Django應用開發。

整個示例項目的完整代碼在Github: .

1. 準備條件

– 在VScode中安裝python插件

– 下載安裝python,在Windows中還需特別注意PATH環境變量的配置

我們的安裝包和開發環境在前面已經都通過conda完成,對比后就可非常明顯的體現出Anaconda在包管理方面的便捷性。

2. 集成虛擬開發環境到VSCode中

  • 在VSCode中按組合鍵ctrl+shift+P,輸入python,先擇Python: Select Interpreter, 這個命令將會展示出一個所有VSCode可用的python解釋器清單。

 

  • 從這個清單中選擇我們上面用conda新建的開發環境 — 以 ./env or .\env開頭

 

 

  •  按組合鍵Ctrl+Shift+`打開一個新的集成命令終端,在VSCode的地步狀態欄,可以看到當前開發環境的標識

 

 

 0x4 – VSCode中創建Django項目

1. 按組合鍵Ctrl+Shift+`進入開發終端,相關解釋器和虛擬開發環境將會自動被激活。然後執行如下命令,如果沒有任何報錯,用瀏覽器打開http://127.0.0.1:8000,我們將會看到Django的默認歡迎頁。

django-admin startproject web_project C:\web_project
cd C:\web_project
python manage.py startapp hello
python manage.py runserver

 

 

 2. 接下來是Django應用的基礎構建

  • hello/views.py
from django.http import HttpResponse

def home(request):
    return HttpResponse("Hello, Django!")
  •  hello/urls.py
from django.urls import path
from hello import views

urlpatterns = [
    path("", views.home, name="home"),
]

 

  • web_project/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("", include("hello.urls")),
]

 

 

 

3. 保存所有文件,然後啟動服務 python manage.py runserver,用瀏覽器訪問應用網址 http://127.0.0.1:8000,將會看到如下:

 

 

 0x5 – VSCode中創建Django debugger launch profile開啟自動調試

到這裏你可能已經在想,是否有更好的方式來調試和運行應用服務,而非每次執行一次python manage.py runserver 呢?必須有的!

VSCode的debugger是支持Django的,我們通過自定義 launch profile就可以實現這一點。

1. 切換左邊的活動欄到Debug, 在Debug視圖的頂部,我們可以看到如下。No Configuratins表示Debugger還未配置任何運行設定(launch.json)。

 

 

 2. 點擊齒輪創建並開啟一個launch.json文件,這個文件裏面已經包含了一些調試設定,每種都是以獨立的JSON對象存在。我們添加如下:

 

{
    "name": "Python: Django",
    "type": "python",
    "request": "launch",
    "program": "${workspaceFolder}/manage.py",
    "console": "integratedTerminal",
    "args": [
        "runserver",
        "--noreload"
    ],
    "django": true
},

 

其中”django”: true告訴VSCode開啟Django頁面模板調試功能。

3. 點擊Debug > Start Debugging按鈕,瀏覽器中打開URL http://127.0.0.1:8000/將可以看到我們的APP順利的跑起來了。

 其實任何時候感覺想要調試一下應用效果時,我們都可以用Debug來啟動服務,此外這個操作還會自動保存所有打開着的文件。

這樣就不用每次都到命令行敲一遍啟動命令,倍爽有木有!!!

4. Debug不僅僅只有啟動和保存功能,我們下面通過具體案例來體驗下高級用法。

  先碼代碼

  • hello/urls.py:添加訪問路由到urlpatterns list中
path("hello/<name>", views.hello_there, name="hello_there"),

 

  • hello/views.py
import re
from datetime import datetime
from django.http import HttpResponse

def home(request):
    return HttpResponse("Hello, Django!")

def hello_there(request, name):
    now = datetime.now()
    formatted_now = now.strftime("%A, %d %B, %Y at %X")

    # Filter the name argument to letters only using regular expressions. URL arguments
    # can contain arbitrary text, so we restrict to safe characters only.
    match_object = re.match("[a-zA-Z]+", name)

    if match_object:
        clean_name = match_object.group(0)
    else:
        clean_name = "Friend"

    content = "Hello there, " + clean_name + "! It's " + formatted_now
    return HttpResponse(content)

5. 在Debug中設定斷點(breakpoints)於now = datetime.now() 所在行。

 

 

 6. 按F5或Debug > Start Debugging 開啟調試,VSCode頂部將會出現一個如下的Debug工具欄。

Pause (or Continue, F5), Step Over (F10), Step Into (F11), Step Out (Shift+F11), Restart (Ctrl+Shift+F5), and Stop (Shift+F5). See  for a description of each command.

 

 

 7. 在下面的終端中也會出現相關的控制信息。通過瀏覽器打開URL http://127.0.0.1:8000/hello/VSCode, 在頁面渲染完成前,VSCode會暫停在設定的斷點處。黃色小箭頭代表其是即將執行到的下一行。

 

 

 點擊 Step Over(F10) 執行 now = datetime.now()所在行。

在左邊Debug菜單欄我們將會看到很多實時輸入信息,包含運行時的變量值等等。我們可以在這裏檢查各個賦值或相關信息是否符合設計目標。

 

 

 程序暫停在斷點位置時,我們可以回到代碼中更改相關語句,調試器中的相關輸入信息也會實時做狀態更新。我們可以嘗試將formatted_now的賦值做如下更改,用來直觀地比較查看下調試器狀態更新。

now.strftime("%a, %d %B, %Y at %X")
'Fri, 07 September, 2018 at 07:46:32'
now.strftime("%a, %d %b, %Y at %X")
'Fri, 07 Sep, 2018 at 07:46:32'
now.strftime("%a, %d %b, %y at %X")
'Fri, 07 Sep, 18 at 07:46:32'

 

 

我們可以按F5逐行執行接下來的語句,並觀察調試器輸出信息,直到最終應用頁面完全渲染完成,點選Debug > Stop Debugging 或 command (Shift+F5)關閉調試。

 

  0x5 – Go to Definition

VSCode也支持查看函數和類的定義查看:

  • Go to Definition jumps from your code into the code that defines an object. For example, in views.py, right-click on HttpResponse in the home function and select Go to Definition (or use F12), which navigates to the class definition in the Django library.

  • Peek Definition (Alt+F12, also on the right-click context menu), is similar, but displays the class definition directly in the editor (making space in the editor window to avoid obscuring any code). Press Escape to close the Peek window or use the x in the upper right corner.

 

 

 

0x6 – Template, Static, Models編程

接下來可以在模板,靜態文件和數據處理的功能編程實現上實踐上面介紹的這些功能,練習整個集成開發環境的操作熟練度。

其實如果有一定基礎的話,我相信一天你就將會從入門到精通。

詳細的代碼個實現步驟在這裏就不在繼續往下貼了。詳細教程大家可按這個鏈接中的內容參照實現。

https://code.visualstudio.com/docs/python/tutorial-django#_create-multiple-templates-that-extend-a-base-template

 

0x7 – 問題分享

整個過程中只有遇到的問題:

1. VSCode無法原生支持Django Models相關對象的關聯檢查。

我們需要額外做點工作:

  • ctrl+shift+`(ESC下面那個鍵)打開命令終端,不用手工敲任何命令,終端會自動切換和激活到對應的虛擬開發環境。

安裝pylint-django

 

  •  然後進入VSCode setting裏面設定pylint參數,具體如下:

 

就這樣,問題解決!

 

 

==========================================================

由於還有繁重的日常工作要忙,這篇文章歷時了幾天時間斷斷續續整理出來,也精簡了不少官方指導中的文字描述。可能會對各位閱讀和操作來帶一些困擾,所以還是建議各位直接去讀官方文檔。我們這裏主要是集中整理了下Anaconda和VSCode的集成開發環境配置,以備未來不時之需,若能順便幫到任何人,將倍感欣慰。各位若有任何問題,歡迎提出,我將會彙整日後自己或其他來源收集到的問題陸續補充到0x7。

 

最後,希望大家能多多動手

多多敲代碼

多多點贊

多多分享

 

回家遛女兒去咯

Over~~

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※公開收購3c價格,不怕被賤賣!

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

Viterbi(維特比)算法在CRF(條件隨機場)中是如何起作用的?

之前我們介紹過BERT+CRF來進行命名實體識別,並對其中的BERT和CRF的概念和作用做了相關的介紹,然對於CRF中的最優的標籤序列的計算原理,我們只提到了維特比算法,並沒有做進一步的解釋,本文將對維特比算法做一個通俗的講解,以便大家更好的理解CRF為什麼能夠得到最優的標籤序列。

通過閱讀本文你將能回答如下問題:

  • 什麼是維特比算法?
  • 為什麼說維特比算法是一種動態規劃算法?
  • 維特比算法具體怎麼實現?

首先,讓我們簡單回顧一下BERT和CRF在命名實體識別中各自的作用:
命名實體識別中,BERT負責學習輸入句子中每個字和符號到對應的實體標籤的規律,而CRF負責學習相鄰實體標籤之間的轉移規則。詳情可以參考這篇文章。該文章中我們對CRF做了簡單易懂的介紹,其中提到CRF的損失函數計算要用到最優路徑,因為CRF的損失函數是求最優路徑的概率占所有路徑概率和的比例,而我們的目標是最大化這個比例。那麼這裏就涉及到計算最優路徑的問題。這裏的路徑在命名實體識別的例子中,就是最終輸出的與句子中的字或符號一 一對應的標籤序列。不同標籤序列的順序組成了不同的路徑。而CRF就是要找出最正確的那條標籤序列路徑,也就是說這條標籤路徑的概率將是所有路徑中最大的,那麼我們可以窮舉出所有可能的標籤路徑,計算出每條路徑的概率和,然後比較出最大的那條,但是這樣做的代價太大了,所以crf選擇了一種稱為維特比的算法來求解此類問題。

維特比算法(英語:Viterbi algorithm)是一種動態規劃算法。它用於尋找最有可能產生觀測事件序列的維特比路徑。

看看下面這個命名實體識別的例子:

上圖共有5層(觀測序列的長度),每層3個節點(狀態的個數),我們的目標就是找到從第一層到第五層的最優路徑。
首先,我們分別計算紅、黃、藍三個節點的輸入連線的概率,以紅色節點舉例,我們先假設紅色節點在最優路徑上,那麼輸入到該節點的三條連線中,概率最大的那條一定在最優路徑上,同理,我們再分別假設黃色和藍色節點在最優路徑上,我們也能各找到一條概率最大的連線,這樣就得到了下面的圖:

然後,我們接着剛才的思路繼續找後面一層的三條最優的連線:

假設找到的最優連線如下:

然後,接着在後面的層應用這個方法:

此時,看上面最後一張圖,我們有了3條候選最優路徑,分別是棕色、綠色和紫色,用標籤來表達如下:

那麼哪條才是最優路徑呢?
就是看哪條路徑的概率和最大,那條路徑就是最優路徑。
但是在實際實現的時候,一般會在計算各層的最優候選連線的時候,就記錄下前繼連線的概率和,並記錄下對應的狀態節點索引(這裏將已經計算出的結果記錄下來供後續使用的方式,就是維特比算法被稱為動態規劃算法的原因),這樣到最後一層的時候,最後一層各候選連線中概率最大的,就是在最優路徑上的那條連線了,然後從這條連線回溯,找出完整的路徑就是最優路徑了。

一直在說概率最大的路徑,那麼這個概率具體指什麼呢?
還記得上一篇文章介紹條件隨機場(CRF)的時候提到,條件隨機場其實是給定了觀測序列的馬爾可夫隨機場,在一階馬爾可夫模型中,定義了以下三個概念:

  • 狀態集合Q,對應到上面的例子就是:
    {B-P, I-P, O}
  • 初始狀態概率向量Π,對應到上面的例子就是:
    {B-P:0.3, I-P:0.2, O:0.5}
    這裏的概率數值是隨便假設的,僅為了方便舉例說明。
  • 狀態轉移概率矩陣A:

CRF中給定了觀測序列做為先驗條件,對應到上面的例子就是:

其中的概率數值同樣是隨便假設的,為了方便舉例。

下圖中紅色節點的概率(可以看成是一個虛擬的開始節點到該節點的連線的概率)的計算方式如下:
初始狀態為B-P的概率Π(B-P) * 該節點的觀測概率P(小|B-P)

下圖中紅色節點的三條連線概率的計算方式如下:
上一層對應節點的概率 * 上層對應節點到該節點的轉移概率 * 該節點的觀測概率P(明|B-P)

其它層之間的節點連線的概率同理計算可得,然後通過上面介紹的維特比算法過程就可以計算出最優路徑了。

ok,本篇就這麼多內容啦~,感謝閱讀O(∩_∩)O。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

salesforce lightning零基礎學習(十五) 公用組件之 獲取表字段的Picklist(多語言),salesforce 零基礎學習(六十二)獲取sObject中類型為Picklist的field values(含record type)

此篇參考:

 我們在lightning中在前台會經常碰到獲取picklist的values然後使用select option進行渲染成下拉列表,此篇用於實現針對指定的sObject以及fieldName(Picklist類型)獲取此字段對應的所有可用的values的公用組件。因為不同的record type可能設置不同的picklist values,所以還有另外的參數設置針對指定的record type developer name去獲取指定的record type對應的Picklist values.

一. PicklistService公用組件聲明實現

Common_PicklistController.cls:有三個形參,其中objectName以及fieldName是必填參數,recordTypeDeveloperName為可選參數。

 1 public without sharing class Common_PicklistController {
 2     
 3     @AuraEnabled(cacheable=true)
 4     public static List<Map<String,String>> getPicklistValues(String objectName, String fieldName,String recordTypeDeveloperName) {
 5         //1. use object and field name get DescribeFieldResult and also get all the picklist values
 6         List<Schema.PicklistEntry> allPicklistValuesByField;
 7         try {
 8             List<Schema.DescribeSobjectResult> objDescriptions = Schema.describeSObjects(new List<String>{objectName});
 9             Schema.SObjectField field = objDescriptions[0].fields.getMap().get(fieldName);
10             Schema.DescribeFieldResult fieldDescribeResult = field.getDescribe();
11             allPicklistValuesByField = fieldDescribeResult.getPicklistValues();
12         } catch (Exception e) {
13             throw new AuraHandledException('Failed to retrieve values : '+ objectName +'.'+ fieldName +': '+ e.getMessage());
14         }
15         
16         //2. get all active field name -> label map
17         List<Map<String,String>> activeOptionMapList = new List<Map<String,String>>();
18         Map<String,String> optionValue2LabelMap = new Map<String,String>();
19         List<String> optionValueList;
20         for(Schema.PicklistEntry entry : allPicklistValuesByField) {
21             if (entry.isActive()) {
22                 System.debug(LoggingLevel.INFO, '*** entry: ' + JSON.serialize(entry));
23                 optionValue2LabelMap.put(entry.getValue(), entry.getLabel());
24             }
25         }
26 
27         //3. generate list with option value(with/without record type)
28         if(String.isNotBlank(recordTypeDeveloperName)) {
29             optionValueList = PicklistDescriber.describe(objectName,recordTypeDeveloperName,fieldName);
30         } else {
31             optionValueList = new List<String>(optionValue2LabelMap.keySet());
32         }
33 
34         //4. generate and format result
35         if(optionValueList != null) {
36             for(String option : optionValueList) {
37                 String optionLabel = optionValue2LabelMap.get(option);
38                 Map<String,String> optionDataMap = new Map<String,String>();
39                 optionDataMap.put('value',option);
40                 optionDataMap.put('label', optionLabel);
41                 activeOptionMapList.add(optionDataMap);
42             }
43         }
44 
45         return activeOptionMapList;
46     }
47 }

 Common_PicklistService.cmp:聲明了getPicklistInfo方法,有以下三個主要參數.objectName對應sObject的API名字,fieldName對應的此sObject中的Picklist類型的字段,recordTypeDeveloperName對應這個sObject的record type的developer name

1 <aura:component access="global" controller="Common_PicklistController">
2     <aura:method access="global" name="getPicklistInfo"  description="Retrieve active picklist values and labels mapping with(without) record type" action="{!c.getPicklistInfoAction}">
3         <aura:attribute type="String" name="objectName" required="true" description="Object name"/>
4         <aura:attribute type="String" name="fieldName" required="true" description="Field name"/>
5         <aura:attribute type="String" name="recordTypeDeveloperName" description="record type developer name"/>
6         <aura:attribute type="Function" name="callback" required="true" description="Callback function that returns the picklist values and labels mapping as [{value: String, label: String}]"/>
7     </aura:method>
8 </aura:component>

 Common_PicklistServiceController.js: 獲取傳遞過來的參數,調用後台方法並對結果放在callback中。

 1 ({
 2     getPicklistInfoAction : function(component, event, helper) {
 3         const params = event.getParam('arguments');
 4         const action = component.get('c.getPicklistValueList');
 5         action.setParams({
 6             objectName : params.objectName,
 7             fieldName : params.fieldName,
 8             recordTypeDeveloperName : params.recordTypeDeveloperName
 9         });
10         action.setCallback(this, function(response) {
11             const state = response.getState();
12             if (state === 'SUCCESS') {
13                 params.callback(response.getReturnValue());
14             } else if (state === 'ERROR') {
15                 console.error('failed to retrieve picklist values for '+ params.objectName +'.'+ params.fieldName);
16                 const errors = response.getError();
17                 if (errors) {
18                     console.error(JSON.stringify(errors));
19                 } else {
20                     console.error('Unknown error');
21                 }
22             }
23         });
24 
25         $A.enqueueAction(action);
26     }
27 })

二. 公用組件調用

 上面介紹了公用組件以後,下面的demo是如何調用。

SimplePicklistDemo引入Common_PicklistService,設置aura:id用於後期獲取到此component,從而調用方法

 1 <aura:component implements="flexipage:availableForAllPageTypes">
 2     <!-- include common picklist service component -->
 3     <c:Common_PicklistService aura:id="service"/>
 4     <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
 5 
 6     <aura:attribute name="accountTypeList" type="List"/>
 7     <aura:attribute name="accountTypeListByRecordType" type="List"/>
 8 
 9     <lightning:layout verticalAlign="center" class="x-large">
10         <lightning:layoutItem flexibility="auto" padding="around-small">
11             <lightning:select label="account type">
12                 <aura:iteration items="{!v.accountTypeList}" var="type">
13                     <option value="{!type.value}" text="{!type.label}"></option>
14                 </aura:iteration>
15             </lightning:select>
16         </lightning:layoutItem>
17 
18         <lightning:layoutItem flexibility="auto" padding="around-small">
19             <lightning:select label="account type with record type">
20                 <aura:iteration items="{!v.accountTypeListByRecordType}" var="type">
21                     <option value="{!type.value}" text="{!type.label}"></option>
22                 </aura:iteration>
23             </lightning:select>
24         </lightning:layoutItem>
25     </lightning:layout>
26     
27 </aura:component>

SimplePicklistDemoController.js:初始化方法用於獲取到公用組件component然後獲取Account的type的values,第一個是獲取所有的values/labels,第二個是獲取指定record type的values/labels。

 1 ({
 2     doInit : function(component, event, helper) {
 3         const service = component.find('service');
 4         service.getPicklistInfo('Account','type','',function(result) {
 5             component.set('v.accountTypeList', result);
 6         });
 7 
 8         service.getPicklistInfo('Account','type','Business_Account',function(result) {
 9             component.set('v.accountTypeListByRecordType',result);
10         });
11     }
12 })

三.效果展示:

1. account type的展示方式

 

 2. account type with record type的展示方式。

 

 總結:篇中介紹了Picklist values針對with/without record type的公用組件的使用,感興趣的可以進行優化,篇中有錯誤的歡迎指出,有不懂的歡迎留言。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

Clean Code 筆記 之 第四章 如何應用註釋

繼上一篇筆記之後,今天我們討論一下 代碼中是存在註釋是否是一件好的事情。

 

在我們開發的過程中講究“名副其實,見名識意”,這也往往是很多公司的要求,但是有了這些要求是不是我們的代碼中如果存在註釋是不是意味着我們的 函數,變量,以及類 的命名就不符合了“名副其實,見名識意”。

我們先區分一下註釋的類別,註釋一般分為以下幾種:

  • 1, 單行註釋
  • 2, 多行註釋
  • 3, 文檔註釋
  • 4, #region 摺疊註釋,可以將 代碼摺疊

 註釋的類別

1, 單行註釋:

在以 “//” 開頭,用以說明一行代碼的作用放置位置 看習慣或者公司要求合理就行。常用於函數內部,在很多的開源代碼中文件的頭部我同樣見到很多人使用單行註釋進行說明,靈活就好。
體現形式如下:

 public List<string> getVipUserNameByUserType()
          {
            // Vip user name list
            var vipUserNames = new List<string>();

            foreach (var user in Users)
            {

                if (user.Type = "VIP")

                    vipUserNames.Add(user.Name);
            }
            return vipUserNames;

          }

View Code

2, 多行註釋:

以“/*”開頭 “*/” 結尾 常用於描述說明一段代碼以及類註釋或者說代碼塊常用於文件的頂部。說明作者信息,時間如果是開源的這包含開源的更多說明。
通常使用如下:

/*
    * 作者:〈版權〉
    * 描述:〈描述〉
    * 創建時間:YYYY-MM-DD
    * 作用:
*/

View Code

3, 文檔註釋:

也就是常用的XML 註釋:它的說明性更加的強烈,多用於類以及函數上,當然屬性上同樣可以使用:
如下所示:

        /// <summary>
        /// MyClass
        /// </summary>
        public class MyClass
        {
            /// <summary>
            /// MyProperty
            /// </summary>
            public int MyProperty { get; set; }
            /// <summary>
            /// MyMethod
            /// </summary>
            public void MyMethod(){  }
        }

View Code

以下是官方建議的文檔標記 點擊標籤會制動跳轉

 

4, #region : 摺疊註釋,常用於描述多個函數的基本作用

書中最喜歡的話

好的註釋不能美化糟糕的代碼,真正好的註釋是你想盡辦法不去謝的註釋。懷註釋都是糟糕代碼的支撐或借口,或者是對錯誤決策的修正。

下面看一個例子

       //Check to see if the employee is eligible for full benefits1)If((employee.flags & HOURLY_FLAG)&& (employee.age>65))

(2)If(employee.isEligibleForFullBenefits()))

  這兩個你更喜歡哪個

View Code

好的註釋的特徵:

1:表示法律信息(這樣的註釋一般出現在文檔頂部說明作用以及協議)

// Copyright (c) .NET Foundation. All rights reserved
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

View Code

2:提供信息的註釋(指無法通過命名提供信息如要 註釋輔助的)

public void ConfigureServices(IServiceCollection services)
{

// These two middleware are registered via an IStartupFilter in UseIISIntegration but you can configure them here.

services.Configure<IISOptions>(options =>
{});

}

View Code

3:對意圖的解釋

4: 警示:告知其他人會出現某種後果的註釋

5: TODO註釋: 主要描述應該做的目前還沒有做的事情。

6: 放大:提示不合理之物的重要性。

應避免的註釋

應該避免以下幾點:

1: 誤導性註釋

2: 日誌式註釋

3: 廢話註釋

4: 標記位置的註釋

5: 括號后的註釋

6: 歸屬與簽名

7: 註釋掉的代碼

8: Html 註釋

以上沒有一一舉例的原因是我的PPT是一份演示的PPT,裏面很多公司的代碼不便貼出,抱歉。

不足之處還請指出

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※公開收購3c價格,不怕被賤賣!

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

ASP.NET Aries 高級開發教程:如何寫WebAPI接口

前提:

最近,有不少同學又問到,Aries里如何提供WebAPI接口?

針對這個問題,今天給順路寫個教程,其實呢,很簡單的。

方式一:直接用WebService提供接口。

用這種方式,直接添加接口就可以了,Aries只是默認處理了.html後綴的請求。對於WS的asmx後綴是沒有影響的,所以傳統怎麼添加就怎麼添加。

 

方式二:單獨用Taurus.MVC寫一個接口項目。

用這種方式,就是把接口獨立成一個項目,然後通過IIS部署成子應用程序就可以了。

只是部署成子應用程序的時候,需要注意一下子目錄和根目錄的web.config,出現重複的只能留根目錄的那個。

(一般都會建議用戶用這種方式,好處是可以在IIS里學會一下怎麼部署子應用程序。)

方式三:在Aries引入Taurus.MVC即可。

這個方式,其實也很簡單,下面介紹一下簡單的部署:

1、引用Taurus.MVC用於寫WebAPI:

在Web.UI項目添加引用Taurus.Core.dll(可以在Nuget上引用,也可以引用源碼項目再引用項目)

2、配置Taurus.MVC的兩個必備項:

A、在HttpModule中添加URL攔截。

//這是原來有的: 
<add name="Aries.Core" type="Aries.Core.UrlRewrite,Aries.Core" />
//這是新添加的:
 <add name="Taurus.Core" type="Taurus.Core.UrlRewrite,Taurus.Core"/>

B、在AppSetting中設置路徑模式:

<!--配置模式【值為0,1或2】[默認為1]
      值為0:匹配{Action}/{Para}
      值為1:匹配{Controller}/{Action}/{Para}
      值為2:匹配{Module}/{Controller}/{Action}/{Para}-->
    <add key="Taurus.RouteMode" value="1"/>

C、在AppSetting中設置接口代碼所在的項目:

<!--指定控制器所在的項目(Dll)名稱(可改,項目編繹的dll叫什麼名就寫什麼)
    <add key="Taurus.Controllers" value="Taurus.Controllers"/>-->

 

如果是用Nuget上引用的,默認都會有上面的兩個,其它默認生的,可以註釋掉。

3、開始寫應用接口代碼:

接口代碼寫在哪裡呢?放在哪個項目都可以,只要上面C點的配置指向就可以了,如果接口代碼分散在多個項目中,配置的value可以用“逗號”分隔。

按Taurus.MVC的方式寫接口,繼承自Taurus.Core.Controller即可:

如:

  /// <summary>
    /// API 接口
    /// </summary>
    public  class APIController : Taurus.Core.Controller
    {
        public void Hello()
        {
            Write("hello Controllers.API");
        }
    }

接口訪問:http://…/api/hello

總結說明:

Aries中默認處理的是.html後綴。

Taurus默認處理的是無後綴。

所以兩者並無衝突,直接引用,加配置就可以了,沒你想的複雜。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

[UWP]用Win2D和CompositionAPI實現文字的發光效果,並製作動畫

1. 成果

獻祭了周末的晚上,成功召喚出了上面的番茄鍾。正當我在感慨“不愧是Shadow大人,這難道就是傳說中的五彩斑斕的黑?”

“那才不是什麼陰影效果,那是發光效果。”被路過的老婆吐槽了。

系系系,老婆說的都系對的。我還以為我在做陰影動畫,現在只好改博客標題了?

要實現上面的動畫效果,首先使用CompositionDrawingSurface,在它上面用DrawTextLayout畫出文字,然後用GaussianBlurEffect模仿成陰影,然後用CanvasActiveLayer裁剪文字的輪廓,然後用這個CompositionDrawingSurface創建出CompositionSurfaceBrush,然後創建一個CompositionMaskBrush,將CompositionSurfaceBrush作為它的Mask,然後用CompositionLinearGradientBrush創建出漸變,再用BlendEffect將它變成四向漸變,再用ColorKeyFrameAnimation和ScalarKeyFrameAnimation在它上面做動畫並把它作為CompositionMaskBrush的Source,然後創建SpriteVisual將CompositionMaskBrush應用上去,然後使用兩個PointLight分別從左到右和從右到左照射這個SpriteVisual,再創建一個AmbientLight模仿呼吸燈。

仔細想想……好吧,老婆說得對,我還真的沒有用到任何Shadow的Api,這裏和Shadow大人半毛錢關係都沒有。

這個番茄鍾源碼可以在這裏查看:

也可以安裝我的番茄鍾應用試玩一下,安裝地址:

這篇文章將介紹其中幾個關鍵技術。

2. 使用GaussianBlurEffect模仿陰影

上一篇文章已經介紹過怎麼在CompositionDrawingSurface上寫字,這裏就不再重複。為了可以為文字添加陰影,需要用到CanvasRenderTargetGaussianBlurEffect

CanvasRenderTarget是一個可以用來畫圖的渲染目標。實現文字陰影的步驟如下:將文字畫到CanvasRenderTarget,然後用它作為GaussianBlurEffect.Source產生一張高斯模糊的圖片,這樣看上去就和文字的陰影一樣。然後再在這張模糊的圖片的前面畫上原本的文字。

代碼如下所示:

using (var session = CanvasComposition.CreateDrawingSession(drawingSurface))
{
    session.Clear(Colors.Transparent);
    using (var textLayout = new CanvasTextLayout(session, Text, textFormat, width, height))
    {
        var bitmap = new CanvasRenderTarget(session, width, height);
        using (var bitmapSession = bitmap.CreateDrawingSession())
        {
            bitmapSession.DrawTextLayout(textLayout, 0, 0, FontColor);
        }
        var blur = new GaussianBlurEffect
        {
            BlurAmount = (float)BlurAmount,
            Source = bitmap,
            BorderMode = EffectBorderMode.Hard
        };

        session.DrawImage(blur, 0, 0);
        session.DrawTextLayout(textLayout, 0, 0, FontColor);
    }
}

效果如下(因為我用了白色字體,這時候已經不怎麼像陰影了):

關於CavasRenderTaget,死魚的有詳細介紹。他的這個專欄的文章都很有趣。

3. 使用CanvasActiveLayer裁剪文字

關於裁剪文字,有幾件事需要做。

首先獲取需要裁剪的文字的輪廓,這使用上一篇文章介紹過的CanvasGeometry.CreateText就可以了,這個函數的返回值是一個。然後使用CanvasGeometry.CreateRectangle獲取整個畫布的CanvasGeometry,將他們用相減得出文字以外的部分,具體代碼如下:

var fullSizeGeometry = CanvasGeometry.CreateRectangle(session, 0, 0, width, height);
var textGeometry = CanvasGeometry.CreateText(textLayout);
var finalGeometry = fullSizeGeometry.CombineWith(textGeometry, Matrix3x2.Identity, CanvasGeometryCombine.Exclude);

這裏之所以不直接使用textGeometry,是因為我們並不是真的裁剪出文字的部分,而是像WPF的那樣用透明度控制显示的部分。就是用來實現這個功能。CanvasDrawingSession.CreateLayer函數使用透明度和CanvasGeometry創建一個CanvasActiveLayer,在創建Layer后CanvasDrawingSession的操作都會應用這個透明度,直到Layer關閉。

using (var layer = session.CreateLayer(1, finalGeometry))
{
    //DrawSth
}

最後效果如下:

關於CanvasActiveLayer的更多用法, 可以參考Lindexi的。

4. 製作有複雜顏色的陰影

如上圖所示,UWP中的DropShadow的Color只能有一種顏色,所以DropShadow不能使用複雜的顏色。這時候就要用到,CompositionMaskBrush有兩個主要屬性:Mask和Source。其中Mask是一個CompositionBrush類型的屬性,它指定不透明的蒙板源。簡單來說,CompositionMaskBrush的形狀就是它的Mask的形狀。而Source屬性則是它的顏色,這個屬性可以是 CompositionColorBrush、CompositionLinearGradientBrush、CompositionSurfaceBrush、CompositionEffectBrush 或 CompositionNineGridBrush 類型的任何 CompositionBrush。可以使用前面創建的CompositionDrawingSurface創建出CompositionSurfaceBrush,最後創建一個CompositionMaskBrush,將CompositionSurfaceBrush作為它的Mask。

var maskBrush = Compositor.CreateMaskBrush();
maskBrush.Mask = Compositor.CreateSurfaceBrush(DrawingSurface);
maskBrush.Source = Compositor.CreateLinearGradientBrush();

本來還想做到大紫大紅的,但被吐槽和本來低調內斂的目的不符合,所以復用了以前的配色,CompositionLinearGradientBrush加BlendEffect做成了有些複雜的配色(但實際上太暗了看不出來):

這時候效果如下:

5. 使用PointLight和AmbientLight製作動畫

我在這篇文章里介紹了PointLight的用法及基本動畫,這次豪華些,同時有從左到右的紅光以及從右到左的藍光,這兩個PointLight的動畫效果大致是這樣:

因為PointLight最多只能疊加兩個,所以再使用AmbientLight並對它的Intensity屬性做動畫,這樣動畫就會變得複雜些,最終實現了文章開頭的動畫。

var compositor = Window.Current.Compositor;
var ambientLight = compositor.CreateAmbientLight();
ambientLight.Intensity = 0;
ambientLight.Color = Colors.White;

var intensityAnimation = compositor.CreateScalarKeyFrameAnimation();
intensityAnimation.InsertKeyFrame(0.2f, 0, compositor.CreateLinearEasingFunction());
intensityAnimation.InsertKeyFrame(0.5f, 0.20f, compositor.CreateLinearEasingFunction());
intensityAnimation.InsertKeyFrame(0.8f, 0, compositor.CreateLinearEasingFunction());
intensityAnimation.Duration = TimeSpan.FromSeconds(10);
intensityAnimation.IterationBehavior = AnimationIterationBehavior.Forever;

ambientLight.StartAnimation(nameof(AmbientLight.Intensity), intensityAnimation);

6. 參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※公開收購3c價格,不怕被賤賣!

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享