Android:為超大圖增加一個(gè)導(dǎo)航圖

地圖

要做的功能如上面demo所示,在圖片縮放時(shí)呵扛,增加一個(gè)展示當(dāng)前局部位置的導(dǎo)航圖每庆。要求是:

  • 支持超大圖;
  • 導(dǎo)航圖紅框定位當(dāng)前局部位置今穿;
  • 導(dǎo)航圖支持滑動(dòng)缤灵,快速定位大圖位置;
  • 支持圖釘顯示蓝晒,隨圖片放大更新透明度腮出。

簡(jiǎn)書(shū)記錄下開(kāi)發(fā)過(guò)程,demo可以在github找到

加載超大圖

測(cè)試用的是一張21m大的圖片(上傳代碼里換了張小的)芝薇,無(wú)論如何不能一次載入內(nèi)存顯示胚嘲。github里找到一個(gè)顯示超大圖的控件subsampling-scale-image-view
,原理是使用Android的BitmapRegionDecoder局部加載圖片洛二。

創(chuàng)建一個(gè)自定義LargeBitmapView馋劈,繼承SubsamplingScaleImageView。加載圖片使用setImage方法晾嘶,圖片來(lái)源可以多種妓雾,通過(guò)ImageSource獲取。

lav_bitmap.setImage(ImageSource.resource(R.mipmap.large_world_map));

監(jiān)聽(tīng)圖片加載過(guò)程使用OnImageEventListener垒迂,回調(diào)豐富械姻。

lav_bitmap.setOnImageEventListener(new SubsamplingScaleImageView.OnImageEventListener() {
    @Override
    public void onReady() {}

    @Override
    public void onImageLoaded() {}

    @Override
    public void onPreviewLoadError(Exception e) {}

    @Override
    public void onImageLoadError(Exception e) {}

    @Override
    public void onTileLoadError(Exception e) {}

    @Override
    public void onPreviewReleased() {}
});

加載導(dǎo)航圖

大圖加載完后調(diào)用showNavigation設(shè)置導(dǎo)航圖,并加載一張縮略的Bitmap机断。導(dǎo)航圖使用自定義的NavigateImageView楷拳,繼承ImageView材部。

private void showNavigation() {
    int navigationWidth = (int) (lav_bitmap.getWidth() * NAVIGATION_SCREEN_WIDTH_SCALE);
    mScale = (float) navigationWidth / lav_bitmap.getSWidth();
    int navigationHeight = (int) (lav_bitmap.getSHeight() * mScale);

    //控件大小
    ViewUtil.setWidth(iv_navigate, navigationWidth);
    ViewUtil.setHeight(iv_navigate, navigationHeight);

    //生成縮略圖
    Bitmap thumbnail = BitmapUtils.decodeSampledBitmapFromResource(getResources(), R.mipmap.large_world_map, navigationWidth, navigationHeight);
    iv_navigate.setImageBitmap(thumbnail);
}

重點(diǎn)要算出導(dǎo)航圖和原圖的比例mScale,然后通過(guò)目標(biāo)寬高獲取縮略圖唯竹。BitmapUtils網(wǎng)上資料很多,就不多介紹苦丁。

導(dǎo)航圖紅框

大圖縮放時(shí)浸颓,導(dǎo)航圖要用紅框展示當(dāng)前局部位置。

lav_bitmap.setOnStateChangedListener(new SubsamplingScaleImageView.OnStateChangedListener() {
    @Override
    public void onScaleChanged(float scale, int orientation) {
    }

    @Override
    public void onCenterChanged(PointF pointF, int orientation) {
    }
});

大圖的變化使用OnStateChangedListener監(jiān)聽(tīng)旺拉,可以獲取縮放比产上、圖片當(dāng)前中點(diǎn)位置和圖片方向的變化。這里只需要知道圖片當(dāng)前中點(diǎn)位置變化就行蛾狗,在onCenterChanged里調(diào)用drawFrame晋涣。

private void drawFrame(PointF pointF) {
    //中點(diǎn)在view位置
    PointF centerInViewPointF = lav_bitmap.sourceToViewCoord(pointF);
    //view的四個(gè)點(diǎn)
    float viewLeft = lav_bitmap.getWidth() / 2 - centerInViewPointF.x;
    float viewTop = lav_bitmap.getHeight() / 2 - centerInViewPointF.y;
    float viewRight = viewLeft + lav_bitmap.getWidth();
    float viewBottom = viewTop + lav_bitmap.getHeight();

    //view對(duì)應(yīng)大圖的位置
    PointF point1 = lav_bitmap.viewToSourceCoord(viewLeft, viewTop);
    PointF point2 = lav_bitmap.viewToSourceCoord(viewRight, viewTop);
    PointF point3 = lav_bitmap.viewToSourceCoord(viewLeft, viewBottom);
    //PointF point4

    //比例
    float left = point1.x * mScale;
    float top = point1.y * mScale;
    float right = point2.x * mScale;
    float bottom = point3.y * mScale;
    iv_navigate.refreshFrame(left, top, right, bottom);
}

這里要分清楚圖片坐標(biāo)和屏幕坐標(biāo),SubsamplingScaleImageView提供sourceToViewCoord和viewToSourceCoord對(duì)兩種坐標(biāo)進(jìn)行轉(zhuǎn)換沉桌。

入?yún)ointF是大圖當(dāng)前中點(diǎn)坐標(biāo)谢鹊,目標(biāo)是得到當(dāng)前圖片局部的四個(gè)角坐標(biāo),所以要獲取控件在屏幕四個(gè)角的坐標(biāo)留凭,然后viewToSourceCoord獲取對(duì)應(yīng)在大圖上的坐標(biāo)佃扼。最后,結(jié)果乘以mScale蔼夜,就是紅框在導(dǎo)航圖上的坐標(biāo)兼耀。

在導(dǎo)航圖上畫(huà)一個(gè)紅色矩形就比較簡(jiǎn)單了,View的measure求冷、layout瘤运、draw工作流程理應(yīng)人人熟悉。

private Paint mPolygonSidePaint = new Paint();
private RectF mFrameRectF = new RectF();

在NavigateImageView里增加兩個(gè)變量匠题,mPolygonSidePaint是畫(huà)筆拯坟,mFrameRectF記錄矩形的四個(gè)坐標(biāo)。

public void refreshFrame(float left, float top, float right, float bottom) {
    if (left < 0) {
        left = 0;
    }
    if (top < 0) {
        top = 0;
    }
    if (right > getWidth()) {
        right = getWidth();
    }
    if (bottom > getHeight()) {
        bottom = getHeight();
    }

    mFrameRectF.set(left, top, right, bottom);
    invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawFrame(canvas);
}

private void drawFrame(Canvas canvas) {
    if (mFrameRectF != null) {
        Path path = new Path();
        path.addRect(mFrameRectF, Path.Direction.CW);
        canvas.drawPath(path, mPolygonSidePaint);
    }
}

refreshFrame設(shè)置了mFrameRectF梧躺,然后重寫onDraw方法似谁,增加drawFrame方法,用drawPath畫(huà)出矩形掠哥。

滑動(dòng)導(dǎo)航圖

導(dǎo)航圖需要支持滑動(dòng)巩踏,對(duì)應(yīng)切換大圖的焦點(diǎn),實(shí)現(xiàn)快速移動(dòng)到大圖某個(gè)局部续搀。

iv_navigate.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        float targetX = event.getX() / mScale;
        float targetY = event.getY() / mScale;
        lav_bitmap.animateCenter(new PointF(targetX, targetY))
                .withDuration(1)
                .start();
        return true;
    }
});

在NavigateImageView的onTouch里增加處理方法塞琼,覆蓋DOWN、MOVE禁舷、UP三種MotionEvent彪杉。將導(dǎo)航圖點(diǎn)擊坐標(biāo)乘以mScale毅往,得到對(duì)應(yīng)大圖中點(diǎn)坐標(biāo),然后調(diào)用animateCenter移動(dòng)到指定局部派近。

增加圖釘

需要在大圖上展示圖釘攀唯,為了避免圖釘非常密集的情況下遮擋圖片,所以初始時(shí)圖釘有一定透明度渴丸,隨著圖片放大侯嘀,減少透明度。

public void refreshPinAlpha() {
   int alpha = (int) (MAX_ALPHA * getScale() / getMaxScale() + INITIAL_ALPHA);
   if (alpha > MAX_ALPHA) {
       alpha = MAX_ALPHA;
   }
   this.mPinAlpha = alpha;
}

private void drawPin(Canvas canvas) {
    mBitmapPaint.setAlpha(mPinAlpha);
    mTextPaint.setAlpha(mPinAlpha);

    for (Pin pin : mPinList) {
        PointF vPointF = sourceToViewCoord(pin.getPointF().x, pin.getPointF().y);
        float vCenterX = vPointF.x - mPinBitmap.getWidth() / 2;
        float vCenterY = vPointF.y - mPinBitmap.getHeight();
        canvas.drawBitmap(mPinBitmap, vCenterX, vCenterY, mBitmapPaint);

        if (!TextUtils.isEmpty(pin.getName())) {
            //獲取字體高度
            Paint.FontMetrics fm = new Paint.FontMetrics();
            mTextPaint.getFontMetrics(fm);
            float fontHeight = fm.top + fm.bottom;
            //顯示名稱
            float textX = vPointF.x + mPinBitmap.getWidth() / 2;
            float textY = vCenterY - fontHeight;
            canvas.drawText(pin.getName(), textX, textY, mTextPaint);
        }
    }
}

圖釘?shù)睦L畫(huà)谱轨,和導(dǎo)航圖紅框的原理是一樣的戒幔,在onDraw里增加drawPin方法。沒(méi)有什么特別要說(shuō)土童,唯一一點(diǎn)是要認(rèn)真通過(guò)計(jì)算诗茎,讓圖釘尖畫(huà)在坐標(biāo)上。

后記

磨刀不誤砍柴献汗,做出了demo敢订,再移到項(xiàng)目中,不過(guò)是個(gè)優(yōu)化過(guò)程雀瓢。后續(xù)繼續(xù)了解圖片局部加載的原理枢析,和回顧View的工作原理。

如果對(duì)你有幫助刃麸,請(qǐng)給我點(diǎn)贊醒叁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泊业,隨后出現(xiàn)的幾起案子把沼,更是在濱河造成了極大的恐慌,老刑警劉巖吁伺,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饮睬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡篮奄,警方通過(guò)查閱死者的電腦和手機(jī)捆愁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窟却,“玉大人昼丑,你說(shuō)我怎么就攤上這事】浜眨” “怎么了菩帝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我呼奢,道長(zhǎng)宜雀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任握础,我火速辦了婚禮辐董,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘禀综。我一直安慰自己郎哭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布菇存。 她就那樣靜靜地躺著,像睡著了一般邦蜜。 火紅的嫁衣襯著肌膚如雪依鸥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天悼沈,我揣著相機(jī)與錄音贱迟,去河邊找鬼。 笑死絮供,一個(gè)胖子當(dāng)著我的面吹牛衣吠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壤靶,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缚俏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了贮乳?” 一聲冷哼從身側(cè)響起忧换,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎向拆,沒(méi)想到半個(gè)月后亚茬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浓恳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年刹缝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颈将。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梢夯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吆鹤,到底是詐尸還是另有隱情厨疙,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站沾凄,受9級(jí)特大地震影響梗醇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撒蟀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一叙谨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧保屯,春花似錦手负、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至切蟋,卻和暖如春统捶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柄粹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工喘鸟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驻右。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓什黑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親堪夭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子愕把,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,280評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件森爽、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,119評(píng)論 4 61
  • 去年有一段時(shí)間拗秘,很明顯感覺(jué)自己整個(gè)人都很焦慮圣絮。 孩子的學(xué)習(xí)上: 平常和小朋友的媽媽們有交流一些各自的育兒情況。尤其...
    彌小木閱讀 268評(píng)論 0 0
  • Merci 南寧的四季并不十分明顯雕旨,分明是秋天卻仍舊帶有夏天的炎熱扮匠,我和幾個(gè)朋友一同坐在這家店里,消磨一整個(gè)午...
    杳Cecilia閱讀 371評(píng)論 0 1
  • 十二點(diǎn)之前睡覺(jué) 每個(gè)月讀完一本書(shū)? 減稱十斤后恢復(fù)低碳水健康飲食 健身房好了堅(jiān)持運(yùn)動(dòng) 拒絕甜食 每周犒勞自己? 控...
    看城市的人閱讀 114評(píng)論 0 0