關(guān)于B站的彈幕集成

好久沒(méi)寫(xiě)博客了,也是因?yàn)樽罱卷?xiàng)目挺忙的!正好這次迭代之后有點(diǎn)時(shí)間,所以寫(xiě)寫(xiě)博客,消磨一下時(shí)間!
現(xiàn)在很多直播軟件都有相應(yīng)的彈幕功能,以前也沒(méi)怎么關(guān)注,最近正好公司的項(xiàng)目中用到了關(guān)于彈幕的內(nèi)容,所以這里正好記錄一下相關(guān)的知識(shí)!

B站DanmakuFlameMaster彈幕的相關(guān)鏈接

本文知識(shí)點(diǎn)

  • DanmakuFlameMaster的集成與簡(jiǎn)單使用
  • DanmakuFlameMaster的進(jìn)階使用

1. DanmakuFlameMaster的集成與簡(jiǎn)單使用

其實(shí)我這個(gè)人真的很笨,最初學(xué)習(xí)這個(gè)的時(shí)候,在網(wǎng)上找了很多文章!但是我都沒(méi)怎么看懂,基本上都是把gitHub里面的內(nèi)容直接粘貼過(guò)來(lái)的,后來(lái)知道怎么弄才大概看明白!或許自己太笨了吧!

1.1 DanmakuFlameMaster的集成

這個(gè)問(wèn)題挺簡(jiǎn)單的,沒(méi)有什么好說(shuō)的,按照github上面集成就可以了!如果你不需要兼容x86和armv5就不用添加最下面兩行的內(nèi)容了!

repositories {
    jcenter()
}

dependencies {
    compile 'com.github.ctiao:DanmakuFlameMaster:0.9.25'
    compile 'com.github.ctiao:ndkbitmap-armv7a:0.9.21'

    # Other ABIs: optional 這個(gè)是適配多種架構(gòu)的,如果你用虛擬機(jī)建議加上
    compile 'com.github.ctiao:ndkbitmap-armv5:0.9.21'
    compile 'com.github.ctiao:ndkbitmap-x86:0.9.21'
}

1.2 DanmakuFlameMaster的簡(jiǎn)單使用

開(kāi)始的時(shí)候需要設(shè)置的內(nèi)容還是很多的,我們一個(gè)一個(gè)來(lái)講解!

1.2.1 布局文件

DanmakuFlameMaster使用多種方式(View/SurfaceView/TextureView)實(shí)現(xiàn)高效繪制!其中分別對(duì)應(yīng)(DanmakuView/DanmakuSurfaceView/DanmakuTextureView)等相關(guān)的View,由于項(xiàng)目中集成的是最簡(jiǎn)單的彈幕功能,所有就沒(méi)有使用關(guān)于SurfaceView類的彈幕控件!

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="addDanmaku"
        android:text="添加彈幕"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <master.flame.danmaku.ui.widget.DanmakuView
        android:id="@+id/dv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn"
        app:layout_constraintVertical_weight="1" />

</android.support.constraint.ConstraintLayout>

布局基本上就是看你們項(xiàng)目中的需求,從而確定相應(yīng)的位置!沒(méi)有什么好說(shuō)的?。?!

1.2.2 設(shè)置相應(yīng)的屬性

一些簡(jiǎn)單的配置,都是DEMO上面有的,注解寫(xiě)的基本上很清楚了,沒(méi)有什么太多好說(shuō)的!

    //設(shè)置最大顯示行數(shù)
    HashMap<Integer, Integer> maxLInesPair = new HashMap<>(16);
    maxLInesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 8);
    //設(shè)置是否禁止重疊
    HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<>(16);
    overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
    overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);
    //創(chuàng)建彈幕上下文
    mContext = DanmakuContext.create();
    //設(shè)置一些相關(guān)的配置
    mContext.setDuplicateMergingEnabled(false)
            //是否重復(fù)合并
            .setScrollSpeedFactor(1.2f)
            //設(shè)置文字的比例
            .setScaleTextSize(1.2f)
            //圖文混排的時(shí)候使用!這里可以不用
            .setCacheStuffer(new MyCacheStuffer(mActivity), mBackgroundCacheStuffer)
            //設(shè)置顯示最大行數(shù)
            .setMaximumLines(maxLInesPair)
            //設(shè)置防,null代表可以重疊
            .preventOverlapping(overlappingEnablePair);
    //設(shè)置解析器
    BaseDanmakuParser defaultDanmakuParser = getDefaultDanmakuParser();

其實(shí)最開(kāi)始我們項(xiàng)目中使用這個(gè)的時(shí)候,所有彈幕都是直接服務(wù)器返回的。所以開(kāi)始的時(shí)候,我的想法是通過(guò)解析器去處理,但是后來(lái)我放棄了!為什么?首先json的解析規(guī)則是很復(fù)雜的,代碼我簡(jiǎn)單看了看,說(shuō)實(shí)話,能力有限真的沒(méi)看懂相應(yīng)的json結(jié)構(gòu),而且即使看懂我,我還要把服務(wù)器的數(shù)據(jù),處理成可用的json結(jié)構(gòu)。我覺(jué)得這樣沒(méi)有必要,所以就開(kāi)啟了一個(gè)線程,添加相應(yīng)的彈幕了!是不是很機(jī)智。。。但是即便是這樣上面那個(gè)設(shè)置解析器的步驟也是不能省略的!

    public static BaseDanmakuParser getDefaultDanmakuParser() {
        return new BaseDanmakuParser() {
            @Override
            protected IDanmakus parse() {
                return new Danmakus();
            }
        };
    }

因?yàn)檫@里解析器沒(méi)有什么作用,所以這里直接按照最簡(jiǎn)單的方法寫(xiě)了一個(gè)解析器!代碼如上:

基本上上面就涵蓋了所有關(guān)于彈幕的配置內(nèi)容了!這里面關(guān)于setCacheStuffer()這個(gè)屬性我之后會(huì)進(jìn)行相應(yīng)的講解!這里你就知道有這么個(gè)東西就行,它主要是處理非文字類型彈幕的!所以如果你要是純文字的話可以不設(shè)置這個(gè)東西!后面會(huì)詳細(xì)講解這個(gè)東西的!

1.2.3 啟動(dòng)相應(yīng)的彈幕

關(guān)于啟動(dòng)彈幕,基本上都走的是相應(yīng)的回調(diào),在彈幕準(zhǔn)備好的時(shí)候,直接調(diào)相應(yīng)的啟動(dòng)方法就好了!

if (mDanmakuView != null) {
    BaseDanmakuParser defaultDanmakuParser = getDefaultDanmakuParser();
    //相應(yīng)的回掉
    mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
        @Override
        public void updateTimer(DanmakuTimer timer) {
            //定時(shí)器更新的時(shí)候回掉
        }

        @Override
        public void drawingFinished() {
            //彈幕繪制完成時(shí)回掉
        }

        @Override
        public void danmakuShown(BaseDanmaku danmaku) {
            //彈幕展示的時(shí)候回掉
        }

        @Override
        public void prepared() {
            //彈幕準(zhǔn)備好的時(shí)候回掉,這里啟動(dòng)彈幕
            mDanmakuView.start();
        }
    });
    mDanmakuView.prepare(defaultDanmakuParser, mContext);
    mDanmakuView.enableDanmakuDrawingCache(true);
}

還有相關(guān)的生命周期方法必須設(shè)置!重要的事情說(shuō)三遍,三遍,三遍!

    @Override
    protected void onPause() {
        super.onPause();
        if (mDanmakuView != null && mDanmakuView.isPrepared()) {
            mDanmakuView.pause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
            mDanmakuView.resume();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDanmakuView != null) {
            // dont forget release!
            mDanmakuView.release();
            mDanmakuView = null;
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (mDanmakuView != null) {
            // dont forget release!
            mDanmakuView.release();
            mDanmakuView = null;
        }
    }

這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)你的屏幕上沒(méi)有任何內(nèi)容,這就對(duì)了!為什么呢?因?yàn)槟氵€沒(méi)添加彈幕呢?。?!所有的準(zhǔn)備工作都做好了,那么我們就開(kāi)始添加彈幕吧!按照B站的指示我們這么配置相關(guān)的彈幕

    private void addDanmaku(boolean islive) {
        //創(chuàng)建一個(gè)彈幕對(duì)象,這里后面的屬性是設(shè)置滾動(dòng)方向的!
        BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
        if (danmaku == null || mDanmakuView == null) {
            return;
        }
        //彈幕顯示的文字
        danmaku.text = "這是一條彈幕" + System.nanoTime();
        //設(shè)置相應(yīng)的邊距,這個(gè)設(shè)置的是四周的邊距
        danmaku.padding = 5;
        // 可能會(huì)被各種過(guò)濾器過(guò)濾并隱藏顯示,若果是本機(jī)發(fā)送的彈幕,建議設(shè)置成1;
        danmaku.priority = 0; 
        //是否是直播彈幕
        danmaku.isLive = islive;
        danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);
        //設(shè)置文字大小
        danmaku.textSize = 25f;
        //設(shè)置文字顏色
        danmaku.textColor = Color.RED;
        //設(shè)置陰影的顏色
        danmaku.textShadowColor = Color.WHITE;
        // danmaku.underlineColor = Color.GREEN;
        //設(shè)置背景顏色
        danmaku.borderColor = Color.GREEN;
        //添加這條彈幕,也就相當(dāng)于發(fā)送
        mDanmakuView.addDanmaku(danmaku);
    }

這樣就成功的發(fā)送了一條文字彈幕了,這里說(shuō)一個(gè)問(wèn)題,最開(kāi)始的時(shí)候,我在想頁(yè)面增加彈幕的時(shí)機(jī)?因?yàn)椴淮嬖诎粹o進(jìn)行添加彈幕,那么只有在mDanmakuView.start();之后進(jìn)行調(diào)用,無(wú)法在生命周期方法中調(diào)用,如果是在生命周期方法中調(diào)用的話,會(huì)存在彈幕為空,不能添加的問(wèn)題!切記。。。這樣整個(gè)流程就穿起來(lái)了!

image

2. DanmakuFlameMaster的進(jìn)階使用

2.1 實(shí)現(xiàn)自定義彈幕的顯示

上面那個(gè)顯示,一般只會(huì)用在視頻直播的內(nèi)容上,但是對(duì)于有頭像的那種彈幕!比如說(shuō),產(chǎn)品跑過(guò)來(lái)說(shuō)!要不添加一個(gè)頭像吧!再加點(diǎn)話術(shù),弄的好看點(diǎn)!就像下面這樣:

image

剛開(kāi)始我在網(wǎng)上找的時(shí)候,很多人都說(shuō)使用SpannableStringBuilder去實(shí)現(xiàn),但是我覺(jué)得如果使用SpannableStringBuilder實(shí)現(xiàn)這個(gè)內(nèi)容的話,很蛋疼的!而且還會(huì)特別費(fèi)盡,如果樣式在復(fù)雜一點(diǎn)的話,那么就更加困難了!下面我們就來(lái)說(shuō)說(shuō)關(guān)于這個(gè)內(nèi)容的實(shí)現(xiàn)!

還記得上面說(shuō)到的關(guān)于圖文有一個(gè)設(shè)置嗎?setCacheStuffer(BaseCacheStuffer cacheStuffer, BaseCacheStuffer.Proxy cacheStufferAdapter) 這個(gè)是針對(duì)非文字的一些顯示樣式的設(shè)置!因?yàn)轫?xiàng)目中要實(shí)現(xiàn)的就是上面這個(gè)樣式,所以我仔細(xì)研究了一下關(guān)于上面這種樣式的顯示方案!

其實(shí)也是很簡(jiǎn)單的!就是重寫(xiě)BaseCacheStuffer類的一些方法而已!怎么實(shí)現(xiàn)的呢?其實(shí)就是自己繪制每條彈幕所顯示的內(nèi)容,這里其實(shí)應(yīng)該是個(gè)策略模式的實(shí)現(xiàn),感興趣的童鞋可以看看!這里就考驗(yàn)Canvas的一些API的使用了!不會(huì)的童鞋可以百度一下!好了,閑扯了這么久了!我們開(kāi)始吧!

我在開(kāi)始的時(shí)候,講過(guò)說(shuō)setCacheStuffer(BaseCacheStuffer cacheStuffer, BaseCacheStuffer.Proxy cacheStufferAdapter)這個(gè)是實(shí)現(xiàn)非文字的方法!所以只要你把上面的兩個(gè)參數(shù)搞懂就可以了!

  • 參數(shù)1:你可以理解為繪制的相應(yīng)處理
  • 參數(shù)2:你可以理解為一個(gè)相應(yīng)繪制的回調(diào)

我們一個(gè)一個(gè)去處理:

2.1.1 實(shí)現(xiàn)相應(yīng)的繪制

繪制的相應(yīng)操作主要是實(shí)現(xiàn)BaseCacheStuffer這個(gè)抽象類,所有關(guān)于繪制的方法都是你自己進(jìn)行實(shí)現(xiàn)的!所以我說(shuō)這里之前最好理解一下相應(yīng)的Canvas這個(gè)類?。?!當(dāng)你繼承這個(gè)抽象類的時(shí)候,你必須實(shí)現(xiàn)三個(gè)相應(yīng)的方法:

public class MyCacheStuffer extends BaseCacheStuffer {
    @Override
    public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) {
        //測(cè)量的相應(yīng)方法
    }

    @Override
    public void clearCaches() {
        //用來(lái)釋放或者清除一些資源
    }

    @Override
    public void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, boolean fromWorkerThread, AndroidDisplayer.DisplayerConfig displayerConfig) {
        //繪制的相應(yīng)方法
    }
}

基本上就是上面的這三個(gè)方法,最主要的就是測(cè)量和繪制的兩個(gè)方法!下面就貼一個(gè)上面顯示內(nèi)容的實(shí)現(xiàn)!

public class MyCacheStuffer extends BaseCacheStuffer {

    /**
     * 文字右邊間距
     */
    private float RIGHTMARGE;
    /**
     * 文字和頭像間距
     */
    private float LEFTMARGE;
    /**
     * 文字和右邊線距離
     */
    private int TEXT_RIGHT_PADDING;
    /**
     * 文字大小
     */
    private float TEXT_SIZE;
    /**
     * 頭像的大小
     */
    private float IMAGEHEIGHT;

    public MyCacheStuffer(Activity activity) {
        // 初始化固定參數(shù),這些參數(shù)可以根據(jù)自己需求自行設(shè)定
        LEFTMARGE = activity.getResources().getDimension(R.dimen.DIMEN_13PX);
        RIGHTMARGE = activity.getResources().getDimension(R.dimen.DIMEN_22PX);
        IMAGEHEIGHT = activity.getResources().getDimension(R.dimen.DIMEN_60PX);
        TEXT_SIZE = activity.getResources().getDimension(R.dimen.DIMEN_24PX);
    }

    @Override
    public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) {
        // 初始化數(shù)據(jù)
        Map<String, Object> map = (Map<String, Object>) danmaku.tag;
        String content = (String) map.get("content");
        Bitmap bitmap = (Bitmap) map.get("bitmap");

        // 設(shè)置畫(huà)筆
        paint.setTextSize(TEXT_SIZE);

        // 計(jì)算名字和內(nèi)容的長(zhǎng)度,取最大值
        float contentWidth = paint.measureText(content);

        // 設(shè)置彈幕區(qū)域的寬度
        danmaku.paintWidth = contentWidth + IMAGEHEIGHT + LEFTMARGE + RIGHTMARGE;
        // 設(shè)置彈幕區(qū)域的高度
        danmaku.paintHeight = IMAGEHEIGHT * 2;
    }

    @Override
    public void clearCaches() {

    }

    @Override
    public void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, boolean fromWorkerThread, AndroidDisplayer.DisplayerConfig displayerConfig) {
        // 初始化數(shù)據(jù)
        Map<String, Object> map = (Map<String, Object>) danmaku.tag;
        String content = (String) map.get("content");
        Bitmap bitmap = (Bitmap) map.get("bitmap");
        String color = (String) map.get("color");

        // 設(shè)置畫(huà)筆
        Paint paint = new Paint();
        paint.setTextSize(TEXT_SIZE);

        //繪制背景
        int textLength = (int) paint.measureText(content);
        //隨機(jī)數(shù),主要是為了生成不同顏色的背景的
        paint.setColor(Color.parseColor(color));

        //獲取圖片的寬度
        float rectBgLeft = left;
        float rectBgTop = top;
        float rectBgRight = left + IMAGEHEIGHT + textLength + LEFTMARGE + RIGHTMARGE;
        float rectBgBottom = top + IMAGEHEIGHT;
        canvas.drawRoundRect(new RectF(rectBgLeft, rectBgTop, rectBgRight, rectBgBottom), IMAGEHEIGHT / 2, IMAGEHEIGHT / 2, paint);

        // 繪制頭像
        float avatorRight = left + IMAGEHEIGHT;
        float avatorBottom = top + IMAGEHEIGHT;
        canvas.drawBitmap(bitmap, null, new RectF(left, top, avatorRight, avatorBottom), paint);

        // 繪制彈幕內(nèi)容,文字白色的
        paint.setColor(Color.WHITE);
        float contentLeft = left + IMAGEHEIGHT + LEFTMARGE;
        //計(jì)算文字的相應(yīng)偏移量
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        //為基線到字體上邊框的距離,即上圖中的top
        float textTop = fontMetrics.top;
        //為基線到字體下邊框的距離,即上圖中的bottom
        float textBottom = fontMetrics.bottom;

        float contentBottom = top + IMAGEHEIGHT / 2;
        //基線中間點(diǎn)的y軸計(jì)算公式
        int baseLineY = (int) (contentBottom - textTop / 2 - textBottom / 2);
        //繪制文字
        canvas.drawText(content, contentLeft, baseLineY, paint);
    }
}

忘了說(shuō)明一下了:我感覺(jué)這里面有一個(gè)地方很巧妙,就是Tag的設(shè)置!可以把許多參數(shù)都攜帶過(guò)來(lái),很不錯(cuò)的想法,當(dāng)然不是我想到的!我也是借鑒別人的。。。一首《無(wú)地自容》-->送給自己!

當(dāng)你添加彈幕的時(shí)候也會(huì)有改動(dòng),但是改動(dòng)的地方很小,像下面這樣!??!

    //創(chuàng)建一條彈幕
    BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);

    if (danmaku == null || mDanmakuView == null) {
        return;
    }

    //設(shè)置相應(yīng)的數(shù)據(jù)
    Bitmap showBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    showBitmap = BitmapUtils.getShowPicture(showBitmap);
    Map<String, Object> map = new HashMap<>(16);
    map.put("content", "這里是顯示的內(nèi)容");
    map.put("bitmap", showBitmap);
    Random random = new Random();
    int randomNum = random.nextInt(mContentColorBg.length);
    map.put("color", mContentColorBg[randomNum]);
    //設(shè)置相應(yīng)的tag
    danmaku.tag = map;
    danmaku.textSize = 0;
    danmaku.padding = 10;
    danmaku.text = "";
    // 一定會(huì)顯示, 一般用于本機(jī)發(fā)送的彈幕
    danmaku.priority = 1;
    danmaku.isLive = false;
    danmaku.setTime(mDanmakuView.getCurrentTime());
    danmaku.textColor = Color.WHITE;
    // 重要:如果有圖文混排,最好不要設(shè)置描邊(設(shè)textShadowColor=0),否則會(huì)進(jìn)行兩次復(fù)雜的繪制導(dǎo)致運(yùn)行效率降低
    danmaku.textShadowColor = 0;
    //添加一條
    mDanmakuView.addDanmaku(danmaku);

這樣就成功的設(shè)置了一條相應(yīng)的彈幕了!但是我發(fā)現(xiàn)一個(gè)問(wèn)題,就是當(dāng)你這么設(shè)置了之后,之前發(fā)送文字的邏輯就要重新制定了!其實(shí)就是多定義一個(gè)類型,根據(jù)不同類型進(jìn)行不同的繪制就好了!很好解決的!這里就不再這里展開(kāi)說(shuō)了!

2.2 相應(yīng)的監(jiān)聽(tīng)問(wèn)題

關(guān)于監(jiān)聽(tīng),其實(shí)就是實(shí)現(xiàn)相應(yīng)的方法,但是還是有必要說(shuō)明一下,怎么處理!

 mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() {
                @Override
                public boolean onDanmakuClick(IDanmakus danmakus) {
                    //點(diǎn)擊事件
                    BaseDanmaku latest = danmakus.last();
                    if (null != latest) {
                        Map<String, Object> map = (Map<String, Object>) latest.tag;
                        //獲取相應(yīng)的數(shù)據(jù)
                        String userId = (String) map.get("content");
                        return true;
                    }
                    return false;
                }

                @Override
                public boolean onDanmakuLongClick(IDanmakus danmakus) {
                    //長(zhǎng)按事件
                    return false;
                }

                @Override
                public boolean onViewClick(IDanmakuView view) {
                    //這個(gè)我沒(méi)有嘗試,但是應(yīng)該是內(nèi)部View的點(diǎn)擊事件吧!猜測(cè)
                    return false;
                }
            });
        }

華麗的分割線


基本上就解決了我們項(xiàng)目中的問(wèn)題,其實(shí)還有很多問(wèn)題我沒(méi)有去處理,這里只是給大家一個(gè)簡(jiǎn)單的案例,如果有什么不對(duì)的還希望指出!我及時(shí)修改。。。如果有什么不懂的,也可以留言!我盡量幫你解決?。?!

忘了,附上代碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,626評(píng)論 1 32
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,872評(píng)論 25 709
  • 兒子學(xué)習(xí)一般般,高中借讀到市重點(diǎn),我和他爸一直都希望他能出國(guó)長(zhǎng)見(jiàn)識(shí)并在那里生活,2010年高二開(kāi)學(xué)的時(shí)候,我...
    楓葉邊的寶貝閱讀 347評(píng)論 0 0
  • 今天中午的時(shí)候,媽媽就一直在聯(lián)系電話,最后,媽媽終于找到了旅游的地方, 哦!原來(lái)是媽媽想帶我...
    楊樊金諾閱讀 1,195評(píng)論 2 4
  • 分享一波美圖 收集而成,并非原創(chuàng)。
    影山文苑閱讀 455評(píng)論 2 3

友情鏈接更多精彩內(nèi)容