├── .gitignore ├── CHANGELOG.md ├── README.md ├── library ├── .classpath ├── .project ├── AndroidManifest.xml ├── project.properties ├── res │ └── values │ │ └── attrs.xml └── src │ └── com │ └── joooonho │ └── SelectableRoundedImageView.java └── sample ├── .classpath ├── .project ├── AndroidManifest.xml ├── libs ├── android-support-v4.jar └── picasso-2.4.0.jar ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── photo_cheetah.jpg ├── drawable-xhdpi │ ├── photo1.jpg │ ├── photo2.jpg │ └── photo3.jpg ├── drawable-xxhdpi │ └── ic_launcher.png ├── layout │ └── activity_main.xml └── values │ └── strings.xml └── src └── com └── joooonho └── MainActivity.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | === 3 | 4 | v1.0.1 *(02.08.2015)* 5 | --- 6 | * Method name modification. "Radiuses" to "Radii" 7 | * Remove unused resource attribute 'sriv_mutate_background' 8 | * Fix a bug: 9 | * Getting rounded accumulatively when recycled by adapter. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SelectableRoundedImageView 2 | ========================== 3 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-SelectableRoundedImageView-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1234) 4 | 5 | 6 | Note that this project is no longer maintained. 7 | 8 | Android ImageView that supports different radii on each corner. It also 9 | supports oval(and circle) shape and border. This would be especially useful for 10 | being used inside CardView which should be rounded only top left and 11 | top right corners(Don't forget to call setPreventCornerOverlap(false) on your cardview). 12 | 13 | I referred to the [RoundedImageView][6], developed by Vince, in developing this new one, and I really appreciate him. Also, I wrote a short article about how I made this library and my thoughts on CardView, check [my blog post][5]. 14 | 15 | Get the sample app on Play Store.
[![Play Store Image](https://camo.githubusercontent.com/dc1ffe0e4d25c2c28a69423c3c78000ef7ee96bf/68747470733a2f2f646576656c6f7065722e616e64726f69642e636f6d2f696d616765732f6272616e642f656e5f6170705f7267625f776f5f34352e706e67)](https://play.google.com/store/apps/details?id=com.joooonho) 16 | 17 | ![SelectableRoundedImageView Sample Screenshots][1] 18 | 19 | Note: When using with [Glide][9], be sure to add asBitmap() chain, like below. 20 | ```java 21 | Glide.with(context) 22 | .load(src) 23 | .asBitmap() 24 | .listener(l) 25 | .into(imageView) 26 | ``` 27 | Note: When using with [Android-Universal-Image-Loader][7], be sure to use SimpleBitmapDisplayer or FadeInBitmapDisplayer rather than RoundedBitmapDisplayer(or RoundedVignetteBitmapDisplayer) when building DisplayImageOptions. See below code. 28 | 29 | ```java 30 | options = new DisplayImageOptions.Builder() 31 | .showImageOnLoading(R.drawable.ic_stub) 32 | .showImageForEmptyUri(R.drawable.ic_empty) 33 | .showImageOnFail(R.drawable.ic_error) 34 | .cacheInMemory(true) 35 | .cacheOnDisk(true) 36 | .considerExifParams(true) 37 | // .displayer(new RoundedBitmapDisplayer(20)) 38 | // DO NOT USE RoundedBitmapDisplayer. Use SimpleBitmapDisplayer! 39 | .displayer(new SimpleBitmapDisplayer()) 40 | .build(); 41 | ``` 42 | 43 | Usage 44 | ---- 45 | Define in xml: 46 | 47 | ```xml 48 | 60 | ``` 61 | 62 | Or in code: 63 | 64 | ```java 65 | SelectableRoundedImageView sriv = new SelectableRoundedImageView(context); 66 | sriv.setScaleType(ScaleType.CENTER_CROP); 67 | sriv.setCornerRadiiDP(4, 4, 0, 0); 68 | sriv.setBorderWidthDP(4); 69 | sriv.setBorderColor(Color.BLUE); 70 | sriv.setImageDrawable(drawable); 71 | sriv.setOval(true); 72 | ``` 73 | 74 | Including In Your Project 75 | ------------------------- 76 | 77 | If you are using Android Studio, SelectableRoundedImageView is available through Gradle. 78 | ``` 79 | dependencies { 80 | compile 'com.joooonho:selectableroundedimageview:1.0.1' 81 | } 82 | ``` 83 | 84 | Also SelectableRoundedImageView is presented as a [library project][3]. You can include 85 | this project by [referencing it as a library project][4] in Eclipse or ant(A standalone JAR 86 | is not possible due to the custom attributes). 87 | 88 | 89 | Developed By 90 | ========================== 91 | 92 | * Joonho Kim - 93 | 94 | License 95 | ------------------------- 96 | 97 | Copyright 2014 Joonho Kim 98 | 99 | Licensed under the Apache License, Version 2.0 (the "License"); 100 | you may not use this file except in compliance with the License. 101 | You may obtain a copy of the License at 102 | 103 | http://www.apache.org/licenses/LICENSE-2.0 104 | 105 | Unless required by applicable law or agreed to in writing, software 106 | distributed under the License is distributed on an "AS IS" BASIS, 107 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 108 | See the License for the specific language governing permissions and 109 | limitations under the License. 110 | 111 | 112 | [1]: http://i.imgur.com/iSizH82.png 113 | [2]: https://play.google.com/store/apps/details?id=com.joooonho 114 | [3]: http://developer.android.com/guide/developing/projects/projects-eclipse.html 115 | [4]: http://developer.android.com/guide/developing/projects/projects-eclipse.html#ReferencingLibraryProject 116 | [5]: http://www.joooooooooonhokim.com/?p=289 117 | [6]: http://github.com/vinc3m1/RoundedImageView 118 | [7]: https://github.com/nostra13/Android-Universal-Image-Loader 119 | [8]: https://github.com/square/picasso 120 | [9]: https://github.com/bumptech/glide 121 | -------------------------------------------------------------------------------- /library/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SelectableRoundedImageViewLib 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-14 15 | android.library=true 16 | -------------------------------------------------------------------------------- /library/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /library/src/com/joooonho/SelectableRoundedImageView.java: -------------------------------------------------------------------------------- 1 | package com.joooonho; 2 | 3 | import com.joooonho.R; 4 | 5 | import android.content.Context; 6 | import android.content.res.ColorStateList; 7 | import android.content.res.Resources; 8 | import android.content.res.Resources.NotFoundException; 9 | import android.content.res.TypedArray; 10 | import android.graphics.Bitmap; 11 | import android.graphics.Bitmap.Config; 12 | import android.graphics.BitmapShader; 13 | import android.graphics.Canvas; 14 | import android.graphics.Color; 15 | import android.graphics.ColorFilter; 16 | import android.graphics.Matrix; 17 | import android.graphics.Paint; 18 | import android.graphics.Path; 19 | import android.graphics.PixelFormat; 20 | import android.graphics.Rect; 21 | import android.graphics.RectF; 22 | import android.graphics.Shader; 23 | import android.graphics.drawable.BitmapDrawable; 24 | import android.graphics.drawable.Drawable; 25 | import android.graphics.drawable.LayerDrawable; 26 | import android.net.Uri; 27 | import android.util.AttributeSet; 28 | import android.util.Log; 29 | import android.widget.ImageView; 30 | 31 | public class SelectableRoundedImageView extends ImageView { 32 | 33 | public static final String TAG = "SelectableRoundedImageView"; 34 | 35 | private int mResource = 0; 36 | 37 | private static final ScaleType[] sScaleTypeArray = { 38 | ScaleType.MATRIX, 39 | ScaleType.FIT_XY, 40 | ScaleType.FIT_START, 41 | ScaleType.FIT_CENTER, 42 | ScaleType.FIT_END, 43 | ScaleType.CENTER, 44 | ScaleType.CENTER_CROP, 45 | ScaleType.CENTER_INSIDE 46 | }; 47 | 48 | // Set default scale type to FIT_CENTER, which is default scale type of 49 | // original ImageView. 50 | private ScaleType mScaleType = ScaleType.FIT_CENTER; 51 | 52 | private float mLeftTopCornerRadius = 0.0f; 53 | private float mRightTopCornerRadius = 0.0f; 54 | private float mLeftBottomCornerRadius = 0.0f; 55 | private float mRightBottomCornerRadius = 0.0f; 56 | 57 | private float mBorderWidth = 0.0f; 58 | private static final int DEFAULT_BORDER_COLOR = Color.BLACK; 59 | private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); 60 | 61 | private boolean isOval = false; 62 | 63 | private Drawable mDrawable; 64 | 65 | private float[] mRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; 66 | 67 | public SelectableRoundedImageView(Context context) { 68 | super(context); 69 | } 70 | 71 | public SelectableRoundedImageView(Context context, AttributeSet attrs) { 72 | this(context, attrs, 0); 73 | } 74 | 75 | public SelectableRoundedImageView(Context context, AttributeSet attrs, int defStyle) { 76 | super(context, attrs, defStyle); 77 | 78 | TypedArray a = context.obtainStyledAttributes(attrs, 79 | R.styleable.SelectableRoundedImageView, defStyle, 0); 80 | 81 | final int index = a.getInt(R.styleable.SelectableRoundedImageView_android_scaleType, -1); 82 | if (index >= 0) { 83 | setScaleType(sScaleTypeArray[index]); 84 | } 85 | 86 | mLeftTopCornerRadius = a.getDimensionPixelSize( 87 | R.styleable.SelectableRoundedImageView_sriv_left_top_corner_radius, 0); 88 | mRightTopCornerRadius = a.getDimensionPixelSize( 89 | R.styleable.SelectableRoundedImageView_sriv_right_top_corner_radius, 0); 90 | mLeftBottomCornerRadius = a.getDimensionPixelSize( 91 | R.styleable.SelectableRoundedImageView_sriv_left_bottom_corner_radius, 0); 92 | mRightBottomCornerRadius = a.getDimensionPixelSize( 93 | R.styleable.SelectableRoundedImageView_sriv_right_bottom_corner_radius, 0); 94 | 95 | if (mLeftTopCornerRadius < 0.0f || mRightTopCornerRadius < 0.0f 96 | || mLeftBottomCornerRadius < 0.0f || mRightBottomCornerRadius < 0.0f) { 97 | throw new IllegalArgumentException("radius values cannot be negative."); 98 | } 99 | 100 | mRadii = new float[] { 101 | mLeftTopCornerRadius, mLeftTopCornerRadius, 102 | mRightTopCornerRadius, mRightTopCornerRadius, 103 | mRightBottomCornerRadius, mRightBottomCornerRadius, 104 | mLeftBottomCornerRadius, mLeftBottomCornerRadius }; 105 | 106 | mBorderWidth = a.getDimensionPixelSize( 107 | R.styleable.SelectableRoundedImageView_sriv_border_width, 0); 108 | if (mBorderWidth < 0) { 109 | throw new IllegalArgumentException("border width cannot be negative."); 110 | } 111 | 112 | mBorderColor = a 113 | .getColorStateList(R.styleable.SelectableRoundedImageView_sriv_border_color); 114 | if (mBorderColor == null) { 115 | mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); 116 | } 117 | 118 | isOval = a.getBoolean(R.styleable.SelectableRoundedImageView_sriv_oval, false); 119 | a.recycle(); 120 | 121 | updateDrawable(); 122 | } 123 | 124 | @Override 125 | protected void drawableStateChanged() { 126 | super.drawableStateChanged(); 127 | invalidate(); 128 | } 129 | 130 | @Override 131 | public ScaleType getScaleType() { 132 | return mScaleType; 133 | } 134 | 135 | @Override 136 | public void setScaleType(ScaleType scaleType) { 137 | super.setScaleType(scaleType); 138 | mScaleType = scaleType; 139 | updateDrawable(); 140 | } 141 | 142 | @Override 143 | public void setImageDrawable(Drawable drawable) { 144 | mResource = 0; 145 | mDrawable = SelectableRoundedCornerDrawable.fromDrawable(drawable, getResources()); 146 | super.setImageDrawable(mDrawable); 147 | updateDrawable(); 148 | } 149 | 150 | @Override 151 | public void setImageBitmap(Bitmap bm) { 152 | mResource = 0; 153 | mDrawable = SelectableRoundedCornerDrawable.fromBitmap(bm, getResources()); 154 | super.setImageDrawable(mDrawable); 155 | updateDrawable(); 156 | } 157 | 158 | @Override 159 | public void setImageResource(int resId) { 160 | if (mResource != resId) { 161 | mResource = resId; 162 | mDrawable = resolveResource(); 163 | super.setImageDrawable(mDrawable); 164 | updateDrawable(); 165 | } 166 | } 167 | 168 | @Override 169 | public void setImageURI(Uri uri) { 170 | super.setImageURI(uri); 171 | setImageDrawable(getDrawable()); 172 | } 173 | 174 | private Drawable resolveResource() { 175 | Resources rsrc = getResources(); 176 | if (rsrc == null) { 177 | return null; 178 | } 179 | 180 | Drawable d = null; 181 | 182 | if (mResource != 0) { 183 | try { 184 | d = rsrc.getDrawable(mResource); 185 | } catch (NotFoundException e) { 186 | Log.w(TAG, "Unable to find resource: " + mResource, e); 187 | // Don't try again. 188 | mResource = 0; 189 | } 190 | } 191 | return SelectableRoundedCornerDrawable.fromDrawable(d, getResources()); 192 | } 193 | 194 | private void updateDrawable() { 195 | if (mDrawable == null) { 196 | return; 197 | } 198 | 199 | ((SelectableRoundedCornerDrawable) mDrawable).setScaleType(mScaleType); 200 | ((SelectableRoundedCornerDrawable) mDrawable).setCornerRadii(mRadii); 201 | ((SelectableRoundedCornerDrawable) mDrawable).setBorderWidth(mBorderWidth); 202 | ((SelectableRoundedCornerDrawable) mDrawable).setBorderColor(mBorderColor); 203 | ((SelectableRoundedCornerDrawable) mDrawable).setOval(isOval); 204 | } 205 | 206 | public float getCornerRadius() { 207 | return mLeftTopCornerRadius; 208 | } 209 | 210 | /** 211 | * Set radii for each corner. 212 | * 213 | * @param leftTop The desired radius for left-top corner in dip. 214 | * @param rightTop The desired desired radius for right-top corner in dip. 215 | * @param leftBottom The desired radius for left-bottom corner in dip. 216 | * @param rightBottom The desired radius for right-bottom corner in dip. 217 | * 218 | */ 219 | public void setCornerRadiiDP(float leftTop, float rightTop, float leftBottom, float rightBottom) { 220 | final float density = getResources().getDisplayMetrics().density; 221 | 222 | final float lt = leftTop * density; 223 | final float rt = rightTop * density; 224 | final float lb = leftBottom * density; 225 | final float rb = rightBottom * density; 226 | 227 | mRadii = new float[] { lt, lt, rt, rt, rb, rb, lb, lb }; 228 | updateDrawable(); 229 | } 230 | 231 | public float getBorderWidth() { 232 | return mBorderWidth; 233 | } 234 | 235 | /** 236 | * Set border width. 237 | * 238 | * @param width 239 | * The desired width in dip. 240 | */ 241 | public void setBorderWidthDP(float width) { 242 | float scaledWidth = getResources().getDisplayMetrics().density * width; 243 | if (mBorderWidth == scaledWidth) { 244 | return; 245 | } 246 | 247 | mBorderWidth = scaledWidth; 248 | updateDrawable(); 249 | invalidate(); 250 | } 251 | 252 | public int getBorderColor() { 253 | return mBorderColor.getDefaultColor(); 254 | } 255 | 256 | public void setBorderColor(int color) { 257 | setBorderColor(ColorStateList.valueOf(color)); 258 | } 259 | 260 | public ColorStateList getBorderColors() { 261 | return mBorderColor; 262 | } 263 | 264 | public void setBorderColor(ColorStateList colors) { 265 | if (mBorderColor.equals(colors)) { 266 | return; 267 | } 268 | 269 | mBorderColor = (colors != null) ? colors : ColorStateList 270 | .valueOf(DEFAULT_BORDER_COLOR); 271 | updateDrawable(); 272 | if (mBorderWidth > 0) { 273 | invalidate(); 274 | } 275 | } 276 | 277 | public boolean isOval() { 278 | return isOval; 279 | } 280 | 281 | public void setOval(boolean oval) { 282 | isOval = oval; 283 | updateDrawable(); 284 | invalidate(); 285 | } 286 | 287 | static class SelectableRoundedCornerDrawable extends Drawable { 288 | 289 | private static final String TAG = "SelectableRoundedCornerDrawable"; 290 | private static final int DEFAULT_BORDER_COLOR = Color.BLACK; 291 | 292 | private RectF mBounds = new RectF(); 293 | private RectF mBorderBounds = new RectF(); 294 | 295 | private final RectF mBitmapRect = new RectF(); 296 | private final int mBitmapWidth; 297 | private final int mBitmapHeight; 298 | 299 | private final Paint mBitmapPaint; 300 | private final Paint mBorderPaint; 301 | 302 | private BitmapShader mBitmapShader; 303 | 304 | private float[] mRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; 305 | private float[] mBorderRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; 306 | 307 | private boolean mOval = false; 308 | 309 | private float mBorderWidth = 0; 310 | private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); 311 | // Set default scale type to FIT_CENTER, which is default scale type of 312 | // original ImageView. 313 | private ScaleType mScaleType = ScaleType.FIT_CENTER; 314 | 315 | private Path mPath = new Path(); 316 | private Bitmap mBitmap; 317 | private boolean mBoundsConfigured = false; 318 | 319 | public SelectableRoundedCornerDrawable(Bitmap bitmap, Resources r) { 320 | mBitmap = bitmap; 321 | mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 322 | 323 | if (bitmap != null) { 324 | mBitmapWidth = bitmap.getScaledWidth(r.getDisplayMetrics()); 325 | mBitmapHeight = bitmap.getScaledHeight(r.getDisplayMetrics()); 326 | } else { 327 | mBitmapWidth = mBitmapHeight = -1; 328 | } 329 | 330 | mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight); 331 | 332 | mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 333 | mBitmapPaint.setStyle(Paint.Style.FILL); 334 | mBitmapPaint.setShader(mBitmapShader); 335 | 336 | mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 337 | mBorderPaint.setStyle(Paint.Style.STROKE); 338 | mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR)); 339 | mBorderPaint.setStrokeWidth(mBorderWidth); 340 | } 341 | 342 | public static SelectableRoundedCornerDrawable fromBitmap(Bitmap bitmap, Resources r) { 343 | if (bitmap != null) { 344 | return new SelectableRoundedCornerDrawable(bitmap, r); 345 | } else { 346 | return null; 347 | } 348 | } 349 | 350 | public static Drawable fromDrawable(Drawable drawable, Resources r) { 351 | if (drawable != null) { 352 | if (drawable instanceof SelectableRoundedCornerDrawable) { 353 | return drawable; 354 | } else if (drawable instanceof LayerDrawable) { 355 | LayerDrawable ld = (LayerDrawable) drawable; 356 | final int num = ld.getNumberOfLayers(); 357 | for (int i = 0; i < num; i++) { 358 | Drawable d = ld.getDrawable(i); 359 | ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d, r)); 360 | } 361 | return ld; 362 | } 363 | 364 | Bitmap bm = drawableToBitmap(drawable); 365 | if (bm != null) { 366 | return new SelectableRoundedCornerDrawable(bm, r); 367 | } else { 368 | Log.w(TAG, "Failed to create bitmap from drawable!"); 369 | } 370 | } 371 | return drawable; 372 | } 373 | 374 | public static Bitmap drawableToBitmap(Drawable drawable) { 375 | if (drawable == null) { 376 | return null; 377 | } 378 | 379 | if (drawable instanceof BitmapDrawable) { 380 | return ((BitmapDrawable) drawable).getBitmap(); 381 | } 382 | 383 | Bitmap bitmap; 384 | int width = Math.max(drawable.getIntrinsicWidth(), 2); 385 | int height = Math.max(drawable.getIntrinsicHeight(), 2); 386 | try { 387 | bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); 388 | Canvas canvas = new Canvas(bitmap); 389 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 390 | drawable.draw(canvas); 391 | } catch (IllegalArgumentException e) { 392 | e.printStackTrace(); 393 | bitmap = null; 394 | } 395 | return bitmap; 396 | } 397 | 398 | @Override 399 | public boolean isStateful() { 400 | return mBorderColor.isStateful(); 401 | } 402 | 403 | @Override 404 | protected boolean onStateChange(int[] state) { 405 | int newColor = mBorderColor.getColorForState(state, 0); 406 | if (mBorderPaint.getColor() != newColor) { 407 | mBorderPaint.setColor(newColor); 408 | return true; 409 | } else { 410 | return super.onStateChange(state); 411 | } 412 | } 413 | 414 | private void configureBounds(Canvas canvas) { 415 | // I have discovered a truly marvelous explanation of this, 416 | // which this comment space is too narrow to contain. :) 417 | // If you want to understand what's going on here, 418 | // See http://www.joooooooooonhokim.com/?p=289 419 | Rect clipBounds = canvas.getClipBounds(); 420 | Matrix canvasMatrix = canvas.getMatrix(); 421 | 422 | if (ScaleType.CENTER == mScaleType) { 423 | mBounds.set(clipBounds); 424 | } else if (ScaleType.CENTER_CROP == mScaleType) { 425 | applyScaleToRadii(canvasMatrix); 426 | mBounds.set(clipBounds); 427 | } else if (ScaleType.FIT_XY == mScaleType) { 428 | Matrix m = new Matrix(); 429 | m.setRectToRect(mBitmapRect, new RectF(clipBounds), Matrix.ScaleToFit.FILL); 430 | mBitmapShader.setLocalMatrix(m); 431 | mBounds.set(clipBounds); 432 | } else if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType 433 | || ScaleType.FIT_CENTER == mScaleType || ScaleType.CENTER_INSIDE == mScaleType) { 434 | applyScaleToRadii(canvasMatrix); 435 | mBounds.set(mBitmapRect); 436 | } else if (ScaleType.MATRIX == mScaleType) { 437 | applyScaleToRadii(canvasMatrix); 438 | mBounds.set(mBitmapRect); 439 | } 440 | } 441 | 442 | private void applyScaleToRadii(Matrix m) { 443 | float[] values = new float[9]; 444 | m.getValues(values); 445 | for (int i = 0; i < mRadii.length; i++) { 446 | mRadii[i] = mRadii[i] / values[0]; 447 | } 448 | } 449 | 450 | private void adjustCanvasForBorder(Canvas canvas) { 451 | Matrix canvasMatrix = canvas.getMatrix(); 452 | final float[] values = new float[9]; 453 | canvasMatrix.getValues(values); 454 | 455 | final float scaleFactorX = values[0]; 456 | final float scaleFactorY = values[4]; 457 | final float translateX = values[2]; 458 | final float translateY = values[5]; 459 | 460 | final float newScaleX = mBounds.width() 461 | / (mBounds.width() + mBorderWidth + mBorderWidth); 462 | final float newScaleY = mBounds.height() 463 | / (mBounds.height() + mBorderWidth + mBorderWidth); 464 | 465 | canvas.scale(newScaleX, newScaleY); 466 | if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType 467 | || ScaleType.FIT_XY == mScaleType || ScaleType.FIT_CENTER == mScaleType 468 | || ScaleType.CENTER_INSIDE == mScaleType || ScaleType.MATRIX == mScaleType) { 469 | canvas.translate(mBorderWidth, mBorderWidth); 470 | } else if (ScaleType.CENTER == mScaleType || ScaleType.CENTER_CROP == mScaleType) { 471 | // First, make translate values to 0 472 | canvas.translate( 473 | -translateX / (newScaleX * scaleFactorX), 474 | -translateY / (newScaleY * scaleFactorY)); 475 | // Then, set the final translate values. 476 | canvas.translate(-(mBounds.left - mBorderWidth), -(mBounds.top - mBorderWidth)); 477 | } 478 | } 479 | 480 | private void adjustBorderWidthAndBorderBounds(Canvas canvas) { 481 | Matrix canvasMatrix = canvas.getMatrix(); 482 | final float[] values = new float[9]; 483 | canvasMatrix.getValues(values); 484 | 485 | final float scaleFactor = values[0]; 486 | 487 | float viewWidth = mBounds.width() * scaleFactor; 488 | mBorderWidth = (mBorderWidth * mBounds.width()) / (viewWidth - (2 * mBorderWidth)); 489 | mBorderPaint.setStrokeWidth(mBorderWidth); 490 | 491 | mBorderBounds.set(mBounds); 492 | mBorderBounds.inset(- mBorderWidth / 2, - mBorderWidth / 2); 493 | } 494 | 495 | private void setBorderRadii() { 496 | for (int i = 0; i < mRadii.length; i++) { 497 | if (mRadii[i] > 0) { 498 | mBorderRadii[i] = mRadii[i]; 499 | mRadii[i] = mRadii[i] - mBorderWidth; 500 | } 501 | } 502 | } 503 | 504 | @Override 505 | public void draw(Canvas canvas) { 506 | canvas.save(); 507 | if (!mBoundsConfigured) { 508 | configureBounds(canvas); 509 | if (mBorderWidth > 0) { 510 | adjustBorderWidthAndBorderBounds(canvas); 511 | setBorderRadii(); 512 | } 513 | mBoundsConfigured = true; 514 | } 515 | 516 | if (mOval) { 517 | if (mBorderWidth > 0) { 518 | adjustCanvasForBorder(canvas); 519 | mPath.addOval(mBounds, Path.Direction.CW); 520 | canvas.drawPath(mPath, mBitmapPaint); 521 | mPath.reset(); 522 | mPath.addOval(mBorderBounds, Path.Direction.CW); 523 | canvas.drawPath(mPath, mBorderPaint); 524 | } else { 525 | mPath.addOval(mBounds, Path.Direction.CW); 526 | canvas.drawPath(mPath, mBitmapPaint); 527 | } 528 | } else { 529 | if (mBorderWidth > 0) { 530 | adjustCanvasForBorder(canvas); 531 | mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW); 532 | canvas.drawPath(mPath, mBitmapPaint); 533 | mPath.reset(); 534 | mPath.addRoundRect(mBorderBounds, mBorderRadii, Path.Direction.CW); 535 | canvas.drawPath(mPath, mBorderPaint); 536 | } else { 537 | mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW); 538 | canvas.drawPath(mPath, mBitmapPaint); 539 | } 540 | } 541 | canvas.restore(); 542 | } 543 | 544 | public void setCornerRadii(float[] radii) { 545 | if (radii == null) 546 | return; 547 | 548 | if (radii.length != 8) { 549 | throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); 550 | } 551 | 552 | for (int i = 0; i < radii.length; i++) { 553 | mRadii[i] = radii[i]; 554 | } 555 | } 556 | 557 | @Override 558 | public int getOpacity() { 559 | return (mBitmap == null || mBitmap.hasAlpha() || mBitmapPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT 560 | : PixelFormat.OPAQUE; 561 | } 562 | 563 | @Override 564 | public void setAlpha(int alpha) { 565 | mBitmapPaint.setAlpha(alpha); 566 | invalidateSelf(); 567 | } 568 | 569 | @Override 570 | public void setColorFilter(ColorFilter cf) { 571 | mBitmapPaint.setColorFilter(cf); 572 | invalidateSelf(); 573 | } 574 | 575 | @Override 576 | public void setDither(boolean dither) { 577 | mBitmapPaint.setDither(dither); 578 | invalidateSelf(); 579 | } 580 | 581 | @Override 582 | public void setFilterBitmap(boolean filter) { 583 | mBitmapPaint.setFilterBitmap(filter); 584 | invalidateSelf(); 585 | } 586 | 587 | @Override 588 | public int getIntrinsicWidth() { 589 | return mBitmapWidth; 590 | } 591 | 592 | @Override 593 | public int getIntrinsicHeight() { 594 | return mBitmapHeight; 595 | } 596 | 597 | public float getBorderWidth() { 598 | return mBorderWidth; 599 | } 600 | 601 | public void setBorderWidth(float width) { 602 | mBorderWidth = width; 603 | mBorderPaint.setStrokeWidth(width); 604 | } 605 | 606 | public int getBorderColor() { 607 | return mBorderColor.getDefaultColor(); 608 | } 609 | 610 | public void setBorderColor(int color) { 611 | setBorderColor(ColorStateList.valueOf(color)); 612 | } 613 | 614 | public ColorStateList getBorderColors() { 615 | return mBorderColor; 616 | } 617 | 618 | /** 619 | * Controls border color of this ImageView. 620 | * 621 | * @param colors 622 | * The desired border color. If it's null, no border will be 623 | * drawn. 624 | * 625 | */ 626 | public void setBorderColor(ColorStateList colors) { 627 | if (colors == null) { 628 | mBorderWidth = 0; 629 | mBorderColor = ColorStateList.valueOf(Color.TRANSPARENT); 630 | mBorderPaint.setColor(Color.TRANSPARENT); 631 | } else { 632 | mBorderColor = colors; 633 | mBorderPaint.setColor(mBorderColor.getColorForState(getState(), 634 | DEFAULT_BORDER_COLOR)); 635 | } 636 | } 637 | 638 | public boolean isOval() { 639 | return mOval; 640 | } 641 | 642 | public void setOval(boolean oval) { 643 | mOval = oval; 644 | } 645 | 646 | public ScaleType getScaleType() { 647 | return mScaleType; 648 | } 649 | 650 | public void setScaleType(ScaleType scaleType) { 651 | if (scaleType == null) { 652 | return; 653 | } 654 | mScaleType = scaleType; 655 | } 656 | } 657 | 658 | } -------------------------------------------------------------------------------- /sample/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SelectableRoundedImageViewSample 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/libs/android-support-v4.jar -------------------------------------------------------------------------------- /sample/libs/picasso-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/libs/picasso-2.4.0.jar -------------------------------------------------------------------------------- /sample/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /sample/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-14 15 | android.library.reference.1=../library 16 | -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/photo_cheetah.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/res/drawable-hdpi/photo_cheetah.jpg -------------------------------------------------------------------------------- /sample/res/drawable-xhdpi/photo1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/res/drawable-xhdpi/photo1.jpg -------------------------------------------------------------------------------- /sample/res/drawable-xhdpi/photo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/res/drawable-xhdpi/photo2.jpg -------------------------------------------------------------------------------- /sample/res/drawable-xhdpi/photo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/res/drawable-xhdpi/photo3.jpg -------------------------------------------------------------------------------- /sample/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pungrue26/SelectableRoundedImageView/86dcc45b4159d39440353997cdfefa5e57626c81/sample/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 26 | 27 | 35 | 36 | 42 | 43 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /sample/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selectable Rounded ImageView 5 | 6 | 7 | -------------------------------------------------------------------------------- /sample/src/com/joooonho/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.joooonho; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.widget.ImageView.ScaleType; 6 | 7 | import com.squareup.picasso.Picasso; 8 | 9 | public class MainActivity extends Activity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | 16 | // All properties can be set in xml. 17 | SelectableRoundedImageView iv0 = (SelectableRoundedImageView) findViewById(R.id.image0); 18 | 19 | // You can set image with resource id. 20 | SelectableRoundedImageView iv1 = (SelectableRoundedImageView) findViewById(R.id.image1); 21 | iv1.setScaleType(ScaleType.CENTER_CROP); 22 | iv1.setOval(true); 23 | iv1.setImageResource(R.drawable.photo_cheetah); 24 | 25 | // Also, You can set image with Picasso. 26 | // This is a normal rectangle imageview. 27 | SelectableRoundedImageView iv2 = (SelectableRoundedImageView) findViewById(R.id.image2); 28 | iv1.setScaleType(ScaleType.CENTER); 29 | Picasso.with(this).load(R.drawable.photo2).into(iv2); 30 | 31 | // Of course, you can set round radius in code. 32 | SelectableRoundedImageView iv3 = (SelectableRoundedImageView) findViewById(R.id.image3); 33 | iv3.setImageDrawable(getResources().getDrawable(R.drawable.photo3)); 34 | ((SelectableRoundedImageView)iv3).setCornerRadiiDP(4, 4, 0, 0); 35 | 36 | } 37 | } 38 | --------------------------------------------------------------------------------