1. 程式人生 > >帶你一步步實現帶有多彩陰影的ImageView

帶你一步步實現帶有多彩陰影的ImageView

身為android開發者,ImageView那一定是玩的滾瓜爛熟,現在如今Material Design設計也越來越流行,給ImageView實現陰影也不是什麼難事,用CardView包裹一下,就能實現了,但是陰影都是一種顏色,實在太單調了╮(╯▽╰)╭,我就在想ImageView陰影能不能設定成圖片自身的顏色呢?,答案當然是可以的,基於這個思路就有了PaletteImageView這個控制元件,PaletteImageView不僅僅可以將陰影設定成圖片自身顏色,還可以解析圖片的顏色,提供可以跟圖片匹配的UI顏色方案,是不是很酷,接下來讓我們一步步來實現它。
先看一下效果圖:
                  這裡寫圖片描述

要實現的功能:

  • 預設情況下為圖片新增相應顏色的陰影
  • 可以自定義陰影的顏色
  • 可以為圖片新增圓角
  • 可以設定陰影半徑大小,提供可以調控的使用者體驗
  • 可以自定義設定陰影分別在x和y方向上的偏移量,更高的可控性
  • 根據圖片自身的顏色提供與圖片匹配的顏色方案(智慧推薦顏色功能)

難點就是怎麼才能獲取到圖片本身的顏色呢?,這就要用到Palette這個類了,Palette就是調色盤的意思,不瞭解的可以參考這篇文章Palette使用詳解
我們先來實現給圖片新增相應顏色的陰影,下面只貼出關鍵程式碼,要閱讀完整程式碼請移步github ,主要思路是解析獲取到的bitmap的顏色,當解析出bitmap顏色的時候,使用handler來通知控制元件來繪製帶有顏色的陰影

在onSizeChanged(…)中能夠獲取到控制元件寬高的時候,對圖片先進行壓縮處理 ,為了防止記憶體溢位情況的發生,然後就是獲取圖片顏色得操作

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        zipBitmap(mImgId, mBitmap, mOnMeasureHeightMode);//先壓縮圖片,再去解析圖片bitmap中的顏色
        mRectFShadow = new
RectF(mPadding, mPadding, getWidth() - mPadding, getHeight() - mPadding);//陰影的載體 ... }

壓縮圖片

  private void zipBitmap(int imgId, Bitmap bitmap, int heightNode) {
        WeakReference<Matrix> weakMatrix = new WeakReference<Matrix>(new Matrix());
        if (weakMatrix.get() == null) return;
        Matrix matrix = weakMatrix.get();
        int reqWidth = getWidth() - mPadding - mPadding;
        int reqHeight = getHeight() - mPadding - mPadding;
        if (reqHeight <= 0 || reqWidth <= 0) return;
        int rawWidth = 0;
        int rawHeight = 0;
        if (imgId != 0 && bitmap == null) {
            WeakReference<BitmapFactory.Options> weakOptions = new WeakReference<BitmapFactory.Options>(new BitmapFactory.Options());
            if (weakOptions.get() == null) return;
            BitmapFactory.Options options = weakOptions.get();
            BitmapFactory.decodeResource(getResources(), imgId, options);
            options.inJustDecodeBounds = true;
            rawWidth = options.outWidth;
            rawHeight = options.outHeight;
            options.inSampleSize = calculateInSampleSize(rawWidth, rawHeight, getWidth() - mPadding * 2, getHeight() - mPadding * 2);
            options.inJustDecodeBounds = false;
            bitmap = BitmapFactory.decodeResource(getResources(), mImgId, options);
        } else if (imgId == 0 && bitmap != null) {
            rawWidth = bitmap.getWidth();
            rawHeight = bitmap.getHeight();
            float scale = rawHeight * 1.0f / rawWidth;
            mRealBitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, (int) (reqWidth * scale), true);
            initShadow(mRealBitmap);
            return;
        }
        if (heightNode == 0) {
            float scale = rawHeight * 1.0f / rawWidth;
            mRealBitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, (int) (reqWidth * scale), true);
        } else {
            int dx = 0;
            int dy = 0;
            int small = Math.min(rawHeight, rawWidth);
            int big = Math.max(reqWidth, reqHeight);
            float scale = big * 1.0f / small;
            matrix.setScale(scale, scale);
            if (rawHeight > rawWidth) {
                dy = (rawHeight - rawWidth) / 2;
            } else if (rawHeight < rawWidth) {
                dx = (rawWidth - rawHeight) / 2;
            }
            mRealBitmap = Bitmap.createBitmap(bitmap, dx, dy, small, small, matrix, true);
        }
        initShadow(mRealBitmap);//解析圖片顏色,關鍵
    }

解析圖片的關鍵程式碼

  private void initShadow(Bitmap bitmap) {
        if (bitmap != null) {
            mAsyncTask = Palette.from(bitmap).generate(paletteAsyncListener);//非同步解析圖片的顏色
        }
    }

      //非同步執行緒中解析圖片顏色的監聽器
       private Palette.PaletteAsyncListener paletteAsyncListener = new Palette.PaletteAsyncListener() {
           @Override
           public void onGenerated(Palette palette) {
               if (palette != null) {
                   ...
                   mMainColor = palette.getDominantSwatch().getRgb();//獲取圖片的主色,也就是預設的陰影顏色
                   mHandler.sendEmptyMessage(MSG);//解析到圖片的顏色,此時傳送重繪的訊息
                   ...
               }
           }
       };

      //處理髮送的訊息
      private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //陰影半徑大小以及x、y方向上陰影偏移量的限制
                if (mOffsetX < DEFAULT_OFFSET) mOffsetX = DEFAULT_OFFSET;
                if (mOffsetY < DEFAULT_OFFSET) mOffsetY = DEFAULT_OFFSET;
                if (mShadowRadius < DEFAULT_SHADOW_RADIUS) mShadowRadius = DEFAULT_SHADOW_RADIUS;
                //設定畫筆陰影的顏色,也就是圖片陰影的顏色了
                mPaintShadow.setShadowLayer(mShadowRadius, mOffsetX, mOffsetY, mMainColor);
                invalidate();//重新繪製控制元件
            }
        };

繪製陰影

 @Override
    protected void onDraw(Canvas canvas) {
        if (mRealBitmap != null) {
            canvas.drawRoundRect(mRectFShadow, mRadius, mRadius, mPaintShadow);//繪製帶有顏色的陰影
            ...
            if (mMainColor != -1) mAsyncTask.cancel(true);//取消解析圖片顏色的非同步任務
        }
    }

經過上面就可以繪製出帶有顏色的陰影,要自定義陰影顏色的話,使用這句程式碼

   public void setShadowColor(int color){
        this.mMainColor = color;
        mHandler.sendEmptyMessage(MSG);//修改了陰影顏色,傳送重新繪製的訊息
    }

接下來我們給圖片設定圓角,在onSizeChange(…)獲取到寬高以及bitmap的同時,我們就建立好了帶有圓角的mRoundBitmap,繪製過陰影后,就繪製可以帶有圓角的圖片

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        ...
        mRoundRectF = new RectF(0, 0, getWidth() - mPadding * 2, getHeight() - mPadding * 2);
        mRoundBitmap = createRoundConerImage(mRealBitmap,mRadius);//建立帶有圓角的bitmap圖片

    }

//建立帶有圓角的bitmap圖片
 private Bitmap createRoundConerImage(Bitmap source, int radius) {
        Bitmap target = Bitmap.createBitmap(getWidth() - mPadding * 2, getHeight() - mPadding * 2, Bitmap.Config.ARGB_4444);
        Canvas canvas = new Canvas(target);
        canvas.drawRoundRect(mRoundRectF, radius, radius, mPaint);
        mPaint.setXfermode(mPorterDuffXfermode);
        canvas.drawBitmap(source, 0, 0, mPaint);
        mPaint.setXfermode(null);
        return target;
    }

 @Override
    protected void onDraw(Canvas canvas) {
        if (mRealBitmap != null) {
            ...
            canvas.drawBitmap(mRoundBitmap, mPadding, mPadding, null);//繪製帶有圓角的bitmap
            if (mMainColor != -1) mAsyncTask.cancel(true);
        }
    }

修改圓角半徑的效果圖

                  這裡寫圖片描述

經過以上帶有多彩顏色陰影的圖片已經出籠了,現在我們要可以修改陰影半徑的大小,修改陰影半徑大小的方式有兩種,一種是在xml中定義,另一種是在程式碼中設定

//xml中新增屬性:
app:paletteShadowRadius 表示陰影半徑

//程式碼中設定陰影半徑大小的原始碼
public void setPaletteShadowRadius(int radius) {
        this.mShadowRadius = radius;//修改陰影半徑大小
        mHandler.sendEmptyMessage(MSG);//傳送訊息,陰影半徑修改了,傳送重繪訊息
    }

修改陰影半徑的效果圖

                  這裡寫圖片描述

修改陰影在x y方向的偏移量的原理跟上面一樣,修改引數再發送訊息重繪

 public void setPaletteShadowOffset(int offsetX, int offsetY) {
        if (offsetX >= mPadding) {
            this.mOffsetX = mPadding;
        } else {
            this.mOffsetX = offsetX;
        }
        if (offsetY > mPadding) {
            this.mOffsetX = mPadding;
        } else {
            this.mOffsetY = offsetY;
        }

        mHandler.sendEmptyMessage(MSG);//陰影在x y方向的偏移量改變了,此時傳送訊息進行重繪。
    }

修改陰影在x y方向的偏移量的效果圖

                 這裡寫圖片描述

現在終於說到智慧配色的地方了,看過Palette使用詳解這篇文章的都知道獲取到顏色方案的過程了,不瞭解的可以先去看看,沒有太神祕的東西O(∩_∩)O哈哈~,下面講一下實現過程:

首先定義一個監聽器,用來監聽圖片顏色的解析過程,解析完成我們返回看控制元件本身的引用,我們可以獲取六種不同顏色主題,每種主題顏色都有與之匹配的標題顏色、正文顏色、背景色。怎麼獲取到這些顏色呢,實現是將每種主題的是三種子顏色放入一個數組中儲存,只要拿到相應的陣列就可以獲取到想要的顏色了。

 public interface OnParseColorListener {
        void onComplete(PaletteImageView paletteImageView);
        void onFail();
    }
//非同步執行緒中解析圖片顏色的監聽器
private Palette.PaletteAsyncListener paletteAsyncListener = new Palette.PaletteAsyncListener() {
         @Override
         public void onGenerated(Palette palette) {
             if (palette != null) {
                 mPalette = palette;
                 mMainColor = palette.getDominantSwatch().getRgb();
                 mHandler.sendEmptyMessage(MSG);
                 if (mListener != null) mListener.onComplete(mInstance);//解析顏色完成
             } else {
                 if (mListener != null) mListener.onFail();//解析顏色失敗
             }
           }
    };

//獲取Vibrant主題的顏色,
public int[] getVibrantColor() {
      if (mPalette == null || mPalette.getVibrantSwatch() == null) return null;
        int[] arry = new int[3];
        arry[0] = mPalette.getVibrantSwatch().getTitleTextColor();
        arry[1] = mPalette.getVibrantSwatch().getBodyTextColor();
        arry[2] = mPalette.getVibrantSwatch().getRgb();
        return arry;
    }

智慧配色效果圖

這裡寫圖片描述

大功告成,可以愉快的玩耍啦。PaletteImageView的具體使用方法,請移步github

相關推薦

步步實現帶有多彩陰影ImageView

身為android開發者,ImageView那一定是玩的滾瓜爛熟,現在如今Material Design設計也越來越流行,給ImageView實現陰影也不是什麼難事,用CardView包裹一下,就能實現了,但是陰影都是一種顏色,實在太單調了╮(╯▽╰)╭,我就在

iOS元件化-步步實現專案的元件化

元件化在業界已經炒的水深火熱,關於元件化的好處和元件化的方案網上已經有大篇的文章了。筆者通過拆分一個現有的demo來簡單聊一下專案實施元件化的過程(將分為上、中、下三篇)。demo可以從github下

Android自定義View的實現方法,步步深入瞭解View(四)

不知不覺中,帶你一步步深入瞭解View系列的文章已經寫到第四篇了,回顧一下,我們一共學習了LayoutInflater的原理分析、檢視的繪製流程、檢視的狀態及重繪等知識,算是把View中很多重要的知識點都涉及到了。如果你還沒有看過我前面的幾篇文章,建議先去閱讀一下,多瞭解一些

Android自定義View的實現方法 步步深入瞭解View 四

                不知不覺中,帶你一步步深入瞭解View系列的文章已經寫到第四篇了,回顧一下,我們一共學習了LayoutInflater的原理分析、檢視的繪製流程、檢視的狀態及重繪等知識,算是把View中很多重要的知識點都涉及到了。如果你還沒有看過我前面的幾篇文章,建議先去閱讀一下,多瞭解一些原

Android LayoutInflater原理分析,步步深入瞭解View

有段時間沒寫部落格了,感覺都有些生疏了呢。最近繁忙的工作終於告一段落,又有時間寫文章了,接下來還會繼續堅持每一週篇的節奏。 有不少朋友跟我反應,都希望我可以寫一篇關於View的文章,講一講View的工作原理以及自定義View的方法。沒錯,承諾過的文章我是一定要兌現的,而且在View這個話題上我還

步步瞭解業務測打款系統的建立

1.專案背景 初始階段 業務方訂單稽核通過後,會有離線任務不斷輪訓向支付中心發起呼叫,支付中心打款處理完成後會返回ifSuccess(是否落庫),state,code,error Message等。如果落庫且code為打款成功,訂單業務狀態會修改為打款成功。 發展階段 為了配合業務發展,會增加各種活動來拉動訂

前端仔教步步實現人人對戰五子棋小遊戲【canvas詳細版】

線上地址--gobang online pc上使用谷歌瀏覽器比較友好@~@ 程式碼倉庫--gobang tutorial 歡迎對此倉庫進行擴充套件或star啦 @~@ 前置知識點: 阮生的es6教程和MDN的canvas教程 以上,兵馬未動,糧草先行。看官可以先體驗下小遊戲並且粗略瞭解

Android檢視繪製流程完全解析,步步深入瞭解View(二)

在上一篇文章中,我帶著大家一起剖析了一下LayoutInflater的工作原理,可以算是對View進行深入瞭解的第一步吧。那麼本篇文章中,我們將繼續對View進行深入探究,看一看它的繪製流程到底是什麼樣的。如果你還沒有看過我的上一篇文章,可以先去閱讀 Android Layo

Android LayoutInflater原理分析,步步深入瞭解View()

有段時間沒寫部落格了,感覺都有些生疏了呢。最近繁忙的工作終於告一段落,又有時間寫文章了,接下來還會繼續堅持每一週篇的節奏。有不少朋友跟我反應,都希望我可以寫一篇關於View的文章,講一講View的工作原理以及自定義View的方法。沒錯,承諾過的文章我是一定要兌現的,而且在Vi

Android檢視狀態及重繪流程分析,步步深入瞭解View(三)

在前面一篇文章中,我帶著大家一起從原始碼的層面上分析了檢視的繪製流程,瞭解了檢視繪製流程中onMeasure、onLayout、onDraw這三個最重要步驟的工作原理,那麼今天我們將繼續對View進行深入探究,學習一下檢視狀態以及重繪方面的知識。如果你還沒有看過我前面一篇文章

Android冷啟動白屏解析,步步分析和解決問題

本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每天都有文章更新。 寫在前面 記得在本月初,我發表了一篇文章叫《 Android Studio新功能解析,你真的瞭解Instant Run嗎?》,裡面詳細講解了

Constraint 程式碼實驗室--步步理解使用 ConstraintLayout

說明 Google I/O 2016 上釋出了 ConstraintLayout, 簡直是要變革 Android 寫介面方式. 於是第二天我立即找到相關文件嘗試, 這是官方提供的 Codelab 專案. 手把手教你探索 ConstraintLayout. 英文原文:

深入python協程的實現層揭開協程的神祕面紗!

協程與 子例程一樣,協程(coroutine)也是一種程式元件。相對子例程而言,協程更為一般和靈活,但在實踐中使用沒有子例程那樣廣泛。協程源自 Simula 和 Modula-2 語言,但也有其他語言支援。 看完之後,我的表情是這樣的: 用專業詞彙解釋專業詞彙,相當於沒說

JAVA實現微信公眾號開發()--接入微信公眾平臺

(一)接入流程解析 在我們的開發過程中無論如何最好的參考工具當然是我們的官方文件了:http://mp.weixin.qq.com/wiki/8/f9a0b8382e0b77d87b3bcc1ce6fbc104.html 通過上面我們可以看出其中接入微信公眾平臺開發,開發者需要按照如下

步步實現 Redis 搜索引擎

行集 準備 exp sta 發的 ast 註意 自己 內容 來源:jasonGeng88 github.com/jasonGeng88/blog/blob/master/201706/redis-search.md 如有好文章投稿,請點擊 → 這裏了解詳情 場景 大家如

WPF步步實現完全無邊框自定義Window(附源碼)

nbsp interop -c 思路 pan cit 最終 auto pre 在我們設計一個軟件的時候,有很多時候我們需要按照美工的設計來重新設計整個版面,這當然包括主窗體,因為WPF為我們提供了強大的模板的特性,這就為我們自定義各種空間提供了可能性,這篇博客主要用來

Python難嗎?華為雲學院探究竟!

數據 對比 pac 常用 靜態 必須 面向對象 如何 開源 Python是一直廣受大家歡迎的編程語言,簡單易學並且功能非常強大。python擁有高效的高級數據結構,並且能夠簡單並且快速的進行面向對象的編程。python的語法簡潔優雅,並且它是動態語言,加上它的語言的可解釋性

分鐘理解閉包--js面向物件程式設計

上一篇《簡單粗暴地理解js原型鏈--js面向物件程式設計》沒想到能攢到這麼多贊,實屬意外。分享是個好事情,尤其是分享自己的學習感悟。所以網上關於原型鏈、閉包、作用域等文章多如牛毛,很多文章寫得很深入很專業,而我卻喜歡用更簡單方式來解說簡單的事情。 什麼是閉包?  先看一段程式碼:

步步實現windows版ijkplayer系列文章之五——使用automake生成makefile

一步步實現windows版ijkplayer系列文章之一——Windows10平臺編譯ffmpeg 4.0.2,生成ffplay 一步步實現windows版ijkplayer系列文章之二——Ijkplayer播放器原始碼分析之音視訊輸出——視訊篇 一步步實現windows版ijkplayer系列文章之三——I

分鐘理解JS閉包——通俗易懂

網上關於閉包的文章有很多,但是大多數都使用了太多專業術語,不便於理解,我在這試著用通俗一點的語言解釋一下何為閉包。 什麼是閉包? 什麼是閉包?閉包是什麼? 先來看一段程式碼: function a(){ var n = 0; function inc() { n+