From c55575e868af5cef462973681377348885962c82 Mon Sep 17 00:00:00 2001 From: Abdul Aris Date: Fri, 21 Aug 2020 09:02:37 +0700 Subject: [PATCH 1/5] update gradle version & plugin version --- build.gradle | 3 +-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index e6b32bc..4f9371c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,8 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - + classpath "com.android.tools.build:gradle:4.0.1" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8794e15..c642520 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 22 12:06:16 WIB 2018 +#Wed Aug 12 12:13:28 ICT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip \ No newline at end of file From 12fc108e8d6607331036a952ca5bb4a0872d1d3e Mon Sep 17 00:00:00 2001 From: Abdul Aris Date: Fri, 21 Aug 2020 09:17:38 +0700 Subject: [PATCH 2/5] migrate to androidx --- build.gradle | 4 +++- circularimageview/build.gradle | 13 +++++++----- .../github/abdularis/civ/AvatarImageView.java | 11 +++++----- .../github/abdularis/civ/CircleImageView.java | 8 ++++---- gradle.properties | 6 ++++++ sample/build.gradle | 20 +++++++++---------- .../abdularis/civsample/MainActivity.java | 5 +++-- .../civsample/PersonListActivity.java | 9 +++++---- .../civsample/PersonListAdapter.java | 3 ++- .../main/res/layout/activity_person_list.xml | 2 +- sample/src/main/res/layout/item_person.xml | 13 +++++++----- 11 files changed, 56 insertions(+), 38 deletions(-) diff --git a/build.gradle b/build.gradle index 4f9371c..a6379e0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - + ext.kotlin_version = "1.3.72" + repositories { google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:4.0.1" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/circularimageview/build.gradle b/circularimageview/build.gradle index 1e7c2de..e2c6e84 100644 --- a/circularimageview/build.gradle +++ b/circularimageview/build.gradle @@ -1,15 +1,18 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 27 + compileSdkVersion 29 + buildToolsVersion "30.0.0" defaultConfig { minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 29 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { @@ -23,6 +26,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation 'com.android.support:support-annotations:27.1.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.annotation:annotation:1.1.0' } diff --git a/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java b/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java index 9f42e9b..ce591dd 100644 --- a/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java +++ b/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java @@ -7,13 +7,14 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; -import android.support.annotation.ColorInt; -import android.support.annotation.Dimension; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.util.AttributeSet; +import androidx.annotation.ColorInt; +import androidx.annotation.Dimension; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java b/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java index 1206041..baaa717 100644 --- a/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java +++ b/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java @@ -17,10 +17,10 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.Dimension; -import android.support.annotation.DrawableRes; -import android.support.annotation.Nullable; +import androidx.annotation.ColorInt; +import androidx.annotation.Dimension; +import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; diff --git a/gradle.properties b/gradle.properties index aac7c9b..d45e398 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,9 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index ef19590..c737dab 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,14 +1,14 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 27 + compileSdkVersion 29 defaultConfig { applicationId "com.github.abdularis.civsample" minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 29 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { release { @@ -24,11 +24,11 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':circularimageview') - implementation 'com.android.support:support-v4:27.1.0' - implementation 'com.android.support:appcompat-v7:27.1.0' - implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.android.support:recyclerview-v7:27.1.0' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/sample/src/main/java/com/github/abdularis/civsample/MainActivity.java b/sample/src/main/java/com/github/abdularis/civsample/MainActivity.java index 9d77087..0dfc3a0 100644 --- a/sample/src/main/java/com/github/abdularis/civsample/MainActivity.java +++ b/sample/src/main/java/com/github/abdularis/civsample/MainActivity.java @@ -1,9 +1,10 @@ package com.github.abdularis.civsample; import android.content.Intent; -import android.databinding.DataBindingUtil; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; import android.graphics.Color; -import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Toast; diff --git a/sample/src/main/java/com/github/abdularis/civsample/PersonListActivity.java b/sample/src/main/java/com/github/abdularis/civsample/PersonListActivity.java index a0bb45e..abce05f 100644 --- a/sample/src/main/java/com/github/abdularis/civsample/PersonListActivity.java +++ b/sample/src/main/java/com/github/abdularis/civsample/PersonListActivity.java @@ -1,10 +1,11 @@ package com.github.abdularis.civsample; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import android.os.Bundle; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; public class PersonListActivity extends AppCompatActivity { diff --git a/sample/src/main/java/com/github/abdularis/civsample/PersonListAdapter.java b/sample/src/main/java/com/github/abdularis/civsample/PersonListAdapter.java index 3021689..86fdca4 100644 --- a/sample/src/main/java/com/github/abdularis/civsample/PersonListAdapter.java +++ b/sample/src/main/java/com/github/abdularis/civsample/PersonListAdapter.java @@ -1,11 +1,12 @@ package com.github.abdularis.civsample; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import com.github.abdularis.civ.AvatarImageView; /** diff --git a/sample/src/main/res/layout/activity_person_list.xml b/sample/src/main/res/layout/activity_person_list.xml index e9592ab..8d0c14f 100644 --- a/sample/src/main/res/layout/activity_person_list.xml +++ b/sample/src/main/res/layout/activity_person_list.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" tools:context="com.github.abdularis.civsample.PersonListActivity"> - diff --git a/sample/src/main/res/layout/item_person.xml b/sample/src/main/res/layout/item_person.xml index 93fb468..93eca30 100644 --- a/sample/src/main/res/layout/item_person.xml +++ b/sample/src/main/res/layout/item_person.xml @@ -1,5 +1,5 @@ - + app:layout_constraintTop_toTopOf="parent" + android:layout_marginLeft="16dp" /> + app:layout_constraintTop_toTopOf="parent" + android:layout_marginLeft="16dp" /> + app:layout_constraintTop_toBottomOf="@+id/text_name" + android:layout_marginLeft="16dp" /> - \ No newline at end of file + \ No newline at end of file From 4373f453ed55dd232e764f17502648de367ead42 Mon Sep 17 00:00:00 2001 From: Abdul Aris Date: Fri, 21 Aug 2020 09:28:42 +0700 Subject: [PATCH 3/5] convert CircleImageView to kotlin --- .../github/abdularis/civ/CircleImageView.java | 322 ------------------ .../github/abdularis/civ/CircleImageView.kt | 280 +++++++++++++++ 2 files changed, 280 insertions(+), 322 deletions(-) delete mode 100644 circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java create mode 100644 circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.kt diff --git a/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java b/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java deleted file mode 100644 index baaa717..0000000 --- a/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.java +++ /dev/null @@ -1,322 +0,0 @@ -package com.github.abdularis.civ; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.ImageView; - -public class CircleImageView extends ImageView { - - private static final int DEF_PRESS_HIGHLIGHT_COLOR = 0x32000000; - - private Shader mBitmapShader; - private Matrix mShaderMatrix; - - private RectF mBitmapDrawBounds; - private RectF mStrokeBounds; - - private Bitmap mBitmap; - - private Paint mBitmapPaint; - private Paint mStrokePaint; - private Paint mPressedPaint; - - private boolean mInitialized; - private boolean mPressed; - private boolean mHighlightEnable; - - public CircleImageView(Context context) { - this(context, null); - } - - public CircleImageView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - - int strokeColor = Color.TRANSPARENT; - float strokeWidth = 0; - boolean highlightEnable = true; - int highlightColor = DEF_PRESS_HIGHLIGHT_COLOR; - - if (attrs != null) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, 0, 0); - - strokeColor = a.getColor(R.styleable.CircleImageView_strokeColor, Color.TRANSPARENT); - strokeWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_strokeWidth, 0); - highlightEnable = a.getBoolean(R.styleable.CircleImageView_highlightEnable, true); - highlightColor = a.getColor(R.styleable.CircleImageView_highlightColor, DEF_PRESS_HIGHLIGHT_COLOR); - - a.recycle(); - } - - mShaderMatrix = new Matrix(); - mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mStrokeBounds = new RectF(); - mBitmapDrawBounds = new RectF(); - mStrokePaint.setColor(strokeColor); - mStrokePaint.setStyle(Paint.Style.STROKE); - mStrokePaint.setStrokeWidth(strokeWidth); - - mPressedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPressedPaint.setColor(highlightColor); - mPressedPaint.setStyle(Paint.Style.FILL); - - mHighlightEnable = highlightEnable; - mInitialized = true; - - setupBitmap(); - } - - @Override - public void setImageResource(@DrawableRes int resId) { - super.setImageResource(resId); - setupBitmap(); - } - - @Override - public void setImageDrawable(@Nullable Drawable drawable) { - super.setImageDrawable(drawable); - setupBitmap(); - } - - @Override - public void setImageBitmap(@Nullable Bitmap bm) { - super.setImageBitmap(bm); - setupBitmap(); - } - - @Override - public void setImageURI(@Nullable Uri uri) { - super.setImageURI(uri); - setupBitmap(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - float halfStrokeWidth = mStrokePaint.getStrokeWidth() / 2f; - updateCircleDrawBounds(mBitmapDrawBounds); - mStrokeBounds.set(mBitmapDrawBounds); - mStrokeBounds.inset(halfStrokeWidth, halfStrokeWidth); - - updateBitmapSize(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setOutlineProvider(new CircleImageViewOutlineProvider(mStrokeBounds)); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean processed = false; - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (!isInCircle(event.getX(), event.getY())) { - return false; - } - processed = true; - mPressed = true; - invalidate(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - processed = true; - mPressed = false; - invalidate(); - if (!isInCircle(event.getX(), event.getY())) { - return false; - } - break; - } - return super.onTouchEvent(event) || processed; - } - - @Override - protected void onDraw(Canvas canvas) { - drawBitmap(canvas); - drawStroke(canvas); - drawHighlight(canvas); - } - - public boolean isHighlightEnable() { - return mHighlightEnable; - } - - public void setHighlightEnable(boolean enable) { - mHighlightEnable = enable; - invalidate(); - } - - @ColorInt - public int getHighlightColor() { - return mPressedPaint.getColor(); - } - - public void setHighlightColor(@ColorInt int color) { - mPressedPaint.setColor(color); - invalidate(); - } - - @ColorInt - public int getStrokeColor() { - return mStrokePaint.getColor(); - } - - public void setStrokeColor(@ColorInt int color) { - mStrokePaint.setColor(color); - invalidate(); - } - - @Dimension - public float getStrokeWidth() { - return mStrokePaint.getStrokeWidth(); - } - - public void setStrokeWidth(@Dimension float width) { - mStrokePaint.setStrokeWidth(width); - invalidate(); - } - - protected void drawHighlight(Canvas canvas) { - if (mHighlightEnable && mPressed) { - canvas.drawOval(mBitmapDrawBounds, mPressedPaint); - } - } - - protected void drawStroke(Canvas canvas) { - if (mStrokePaint.getStrokeWidth() > 0f) { - canvas.drawOval(mStrokeBounds, mStrokePaint); - } - } - - protected void drawBitmap(Canvas canvas) { - canvas.drawOval(mBitmapDrawBounds, mBitmapPaint); - } - - protected void updateCircleDrawBounds(RectF bounds) { - float contentWidth = getWidth() - getPaddingLeft() - getPaddingRight(); - float contentHeight = getHeight() - getPaddingTop() - getPaddingBottom(); - - float left = getPaddingLeft(); - float top = getPaddingTop(); - if (contentWidth > contentHeight) { - left += (contentWidth - contentHeight) / 2f; - } else { - top += (contentHeight - contentWidth) / 2f; - } - - float diameter = Math.min(contentWidth, contentHeight); - bounds.set(left, top, left + diameter, top + diameter); - } - - private void setupBitmap() { - if (!mInitialized) { - return; - } - mBitmap = getBitmapFromDrawable(getDrawable()); - if (mBitmap == null) { - return; - } - - mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - mBitmapPaint.setShader(mBitmapShader); - - updateBitmapSize(); - } - - private void updateBitmapSize() { - if (mBitmap == null) return; - - float dx; - float dy; - float scale; - - // scale up/down with respect to this view size and maintain aspect ratio - // translate bitmap position with dx/dy to the center of the image - if (mBitmap.getWidth() < mBitmap.getHeight()) { - scale = mBitmapDrawBounds.width() / (float)mBitmap.getWidth(); - dx = mBitmapDrawBounds.left; - dy = mBitmapDrawBounds.top - (mBitmap.getHeight() * scale / 2f) + (mBitmapDrawBounds.width() / 2f); - } else { - scale = mBitmapDrawBounds.height() / (float)mBitmap.getHeight(); - dx = mBitmapDrawBounds.left - (mBitmap.getWidth() * scale / 2f) + (mBitmapDrawBounds.width() / 2f); - dy = mBitmapDrawBounds.top; - } - mShaderMatrix.setScale(scale, scale); - mShaderMatrix.postTranslate(dx, dy); - mBitmapShader.setLocalMatrix(mShaderMatrix); - } - - private Bitmap getBitmapFromDrawable(Drawable drawable) { - if (drawable == null) { - return null; - } - - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - } - - Bitmap bitmap = Bitmap.createBitmap( - drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } - - private boolean isInCircle(float x, float y) { - // find the distance between center of the view and x,y point - double distance = Math.sqrt( - Math.pow(mBitmapDrawBounds.centerX() - x, 2) + Math.pow(mBitmapDrawBounds.centerY() - y, 2) - ); - return distance <= (mBitmapDrawBounds.width() / 2); - } - - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public class CircleImageViewOutlineProvider extends ViewOutlineProvider { - - private Rect mRect; - - CircleImageViewOutlineProvider(RectF rect) { - mRect = new Rect( - (int) rect.left, - (int) rect.top, - (int) rect.right, - (int) rect.bottom - ); - } - - @Override - public void getOutline(View view, Outline outline) { - outline.setOval(mRect); - } - - } -} diff --git a/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.kt b/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.kt new file mode 100644 index 0000000..8e99374 --- /dev/null +++ b/circularimageview/src/main/java/com/github/abdularis/civ/CircleImageView.kt @@ -0,0 +1,280 @@ +package com.github.abdularis.civ + +import android.annotation.TargetApi +import android.content.Context +import android.graphics.* +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Build +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.view.ViewOutlineProvider +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.Dimension +import androidx.annotation.DrawableRes +import kotlin.math.min +import kotlin.math.pow +import kotlin.math.sqrt + +open class CircleImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : ImageView(context, attrs) { + + private var mBitmapShader: Shader? = null + private val mShaderMatrix: Matrix + private val mBitmapDrawBounds: RectF + private val mStrokeBounds: RectF + private var mBitmap: Bitmap? = null + private val mBitmapPaint: Paint + private val mStrokePaint: Paint + private val mPressedPaint: Paint + private val mInitialized: Boolean + private var mPressed = false + private var mHighlightEnable: Boolean + + var isHighlightEnable: Boolean + get() = mHighlightEnable + set(enable) { + mHighlightEnable = enable + invalidate() + } + + @get:ColorInt + var highlightColor: Int + get() = mPressedPaint.color + set(color) { + mPressedPaint.color = color + invalidate() + } + + @get:ColorInt + var strokeColor: Int + get() = mStrokePaint.color + set(color) { + mStrokePaint.color = color + invalidate() + } + + @get:Dimension + var strokeWidth: Float + get() = mStrokePaint.strokeWidth + set(width) { + mStrokePaint.strokeWidth = width + invalidate() + } + + override fun setImageResource(@DrawableRes resId: Int) { + super.setImageResource(resId) + setupBitmap() + } + + override fun setImageDrawable(drawable: Drawable?) { + super.setImageDrawable(drawable) + setupBitmap() + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + setupBitmap() + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + setupBitmap() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + val halfStrokeWidth = mStrokePaint.strokeWidth / 2f + updateCircleDrawBounds(mBitmapDrawBounds) + mStrokeBounds.set(mBitmapDrawBounds) + mStrokeBounds.inset(halfStrokeWidth, halfStrokeWidth) + updateBitmapSize() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + outlineProvider = CircleImageViewOutlineProvider(mStrokeBounds) + } + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + var processed = false + when (event.action) { + MotionEvent.ACTION_DOWN -> { + if (!isInCircle(event.x, event.y)) { + return false + } + processed = true + mPressed = true + invalidate() + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + processed = true + mPressed = false + invalidate() + if (!isInCircle(event.x, event.y)) { + return false + } + } + } + return super.onTouchEvent(event) || processed + } + + override fun onDraw(canvas: Canvas) { + drawBitmap(canvas) + drawStroke(canvas) + drawHighlight(canvas) + } + + protected fun drawHighlight(canvas: Canvas) { + if (mHighlightEnable && mPressed) { + canvas.drawOval(mBitmapDrawBounds, mPressedPaint) + } + } + + protected fun drawStroke(canvas: Canvas) { + if (mStrokePaint.strokeWidth > 0f) { + canvas.drawOval(mStrokeBounds, mStrokePaint) + } + } + + private fun drawBitmap(canvas: Canvas) { + canvas.drawOval(mBitmapDrawBounds, mBitmapPaint) + } + + protected fun updateCircleDrawBounds(bounds: RectF) { + val contentWidth = width - paddingLeft - paddingRight.toFloat() + val contentHeight = + height - paddingTop - paddingBottom.toFloat() + var left = paddingLeft.toFloat() + var top = paddingTop.toFloat() + if (contentWidth > contentHeight) { + left += (contentWidth - contentHeight) / 2f + } else { + top += (contentHeight - contentWidth) / 2f + } + val diameter = min(contentWidth, contentHeight) + bounds[left, top, left + diameter] = top + diameter + } + + private fun setupBitmap() { + if (!mInitialized) { + return + } + mBitmap = getBitmapFromDrawable(drawable) + if (mBitmap == null) { + return + } + mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + mBitmapPaint.shader = mBitmapShader + updateBitmapSize() + } + + private fun updateBitmapSize() { + if (mBitmap == null) return + val dx: Float + val dy: Float + val scale: Float + + // scale up/down with respect to this view size and maintain aspect ratio + // translate bitmap position with dx/dy to the center of the image + if (mBitmap!!.width < mBitmap!!.height) { + scale = mBitmapDrawBounds.width() / mBitmap!!.width.toFloat() + dx = mBitmapDrawBounds.left + dy = + mBitmapDrawBounds.top - mBitmap!!.height * scale / 2f + mBitmapDrawBounds.width() / 2f + } else { + scale = mBitmapDrawBounds.height() / mBitmap!!.height.toFloat() + dx = + mBitmapDrawBounds.left - mBitmap!!.width * scale / 2f + mBitmapDrawBounds.width() / 2f + dy = mBitmapDrawBounds.top + } + mShaderMatrix.setScale(scale, scale) + mShaderMatrix.postTranslate(dx, dy) + mBitmapShader!!.setLocalMatrix(mShaderMatrix) + } + + private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? { + if (drawable == null) { + return null + } + if (drawable is BitmapDrawable) { + return drawable.bitmap + } + val bitmap = Bitmap.createBitmap( + drawable.intrinsicWidth, + drawable.intrinsicHeight, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + return bitmap + } + + private fun isInCircle(x: Float, y: Float): Boolean { + // find the distance between center of the view and x,y point + val distance = sqrt( + (mBitmapDrawBounds.centerX() - x.toDouble()).pow(2.0) + (mBitmapDrawBounds.centerY() - y.toDouble()).pow(2.0) + ) + return distance <= mBitmapDrawBounds.width() / 2 + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + class CircleImageViewOutlineProvider internal constructor(rect: RectF) : ViewOutlineProvider() { + private val mRect = Rect( + rect.left.toInt(), + rect.top.toInt(), + rect.right.toInt(), + rect.bottom.toInt() + ) + + override fun getOutline(view: View, outline: Outline) { + outline.setOval(mRect) + } + } + + companion object { + private const val DEF_PRESS_HIGHLIGHT_COLOR = 0x32000000 + } + + init { + var strokeColor = Color.TRANSPARENT + var strokeWidth = 0f + var highlightEnable = true + var highlightColor = + DEF_PRESS_HIGHLIGHT_COLOR + attrs?.let { + val a = + context.obtainStyledAttributes(it, R.styleable.CircleImageView, 0, 0) + strokeColor = a.getColor( + R.styleable.CircleImageView_strokeColor, + Color.TRANSPARENT + ) + strokeWidth = + a.getDimensionPixelSize(R.styleable.CircleImageView_strokeWidth, 0).toFloat() + highlightEnable = a.getBoolean(R.styleable.CircleImageView_highlightEnable, true) + highlightColor = a.getColor( + R.styleable.CircleImageView_highlightColor, + DEF_PRESS_HIGHLIGHT_COLOR + ) + a.recycle() + } + mShaderMatrix = Matrix() + mBitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mStrokePaint = Paint(Paint.ANTI_ALIAS_FLAG) + mStrokeBounds = RectF() + mBitmapDrawBounds = RectF() + mStrokePaint.color = strokeColor + mStrokePaint.style = Paint.Style.STROKE + mStrokePaint.strokeWidth = strokeWidth + mPressedPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mPressedPaint.color = highlightColor + mPressedPaint.style = Paint.Style.FILL + mHighlightEnable = highlightEnable + mInitialized = true + setupBitmap() + } +} \ No newline at end of file From aa65f0fc1f9b4a9113ed2d7465211a26b7b5d2d0 Mon Sep 17 00:00:00 2001 From: Abdul Aris Date: Fri, 21 Aug 2020 09:45:24 +0700 Subject: [PATCH 4/5] convert AvatarImageView to kotlin --- .../github/abdularis/civ/AvatarImageView.java | 187 ------------------ .../github/abdularis/civ/AvatarImageView.kt | 141 +++++++++++++ 2 files changed, 141 insertions(+), 187 deletions(-) delete mode 100644 circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java create mode 100644 circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.kt diff --git a/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java b/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java deleted file mode 100644 index ce591dd..0000000 --- a/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.github.abdularis.civ; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; - -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Created by abdularis on 22/03/18. - */ - -public class AvatarImageView extends CircleImageView { - - public static final int SHOW_INITIAL = 1; - public static final int SHOW_IMAGE = 2; - - @IntDef({SHOW_INITIAL, SHOW_IMAGE}) - @Retention(RetentionPolicy.SOURCE) - public @interface State { - } - - private static final String DEF_INITIAL = "A"; - private static final int DEF_TEXT_SIZE = 90; - private static final int DEF_BACKGROUND_COLOR = 0xE53935; - @State - private static final int DEF_STATE = SHOW_INITIAL; - - private Paint mTextPaint; - private Rect mTextBounds; - - private Paint mBackgroundPaint; - private RectF mBackgroundBounds; - - @NonNull - private String mInitial; - @NonNull - private String mText; - - private int mShowState; - - public AvatarImageView(Context context) { - this(context, null); - } - - public AvatarImageView(Context context, AttributeSet attrs) { - super(context, attrs); - - String text = DEF_INITIAL; - int textColor = Color.WHITE; - int textSize = DEF_TEXT_SIZE; - int backgroundColor = DEF_BACKGROUND_COLOR; - int showState = DEF_STATE; - - if (attrs != null) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0); - - text = a.getString(R.styleable.AvatarImageView_text); - textColor = a.getColor(R.styleable.AvatarImageView_textColor, textColor); - textSize = a.getDimensionPixelSize(R.styleable.AvatarImageView_textSize, textSize); - backgroundColor = a.getColor(R.styleable.AvatarImageView_avatarBackgroundColor, backgroundColor); - showState = a.getInt(R.styleable.AvatarImageView_view_state, showState); - - a.recycle(); - } - - mShowState = showState; - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTextPaint.setTextAlign(Paint.Align.CENTER); - mTextPaint.setColor(textColor); - mTextPaint.setTextSize(textSize); - - mTextBounds = new Rect(); - mText = text == null ? "" : text; - mInitial = extractInitial(text); - updateTextBounds(); - - mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBackgroundPaint.setColor(backgroundColor); - mBackgroundPaint.setStyle(Paint.Style.FILL); - - mBackgroundBounds = new RectF(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - updateCircleDrawBounds(mBackgroundBounds); - } - - @Override - protected void onDraw(Canvas canvas) { - if (mShowState == SHOW_INITIAL) { - float textBottom = mBackgroundBounds.centerY() - mTextBounds.exactCenterY(); - canvas.drawOval(mBackgroundBounds, mBackgroundPaint); - canvas.drawText(mInitial, mBackgroundBounds.centerX(), textBottom, mTextPaint); - drawStroke(canvas); - drawHighlight(canvas); - } else { - super.onDraw(canvas); - } - } - - @NonNull - public String getInitial() { - return mInitial; - } - - @NonNull - public String getText() { - return mText; - } - - public void setText(@Nullable String text) { - mText = text == null ? "" : text; - mInitial = extractInitial(text); - updateTextBounds(); - invalidate(); - } - - @State - public int getState() { - return mShowState; - } - - public void setState(@State int state) { - if (state != SHOW_INITIAL && state != SHOW_IMAGE) { - String msg = "Illegal avatar state value: " + state + ", use either SHOW_INITIAL or SHOW_IMAGE constant"; - throw new IllegalArgumentException(msg); - } - mShowState = state; - invalidate(); - } - - @Dimension - public float getTextSize() { - return mTextPaint.getTextSize(); - } - - public void setTextSize(@Dimension float size) { - mTextPaint.setTextSize(size); - updateTextBounds(); - invalidate(); - } - - @ColorInt - public int getTextColor() { - return mTextPaint.getColor(); - } - - public void setTextColor(@ColorInt int color) { - mTextPaint.setColor(color); - invalidate(); - } - - @ColorInt - public int getAvatarBackgroundColor() { - return mBackgroundPaint.getColor(); - } - - public void setAvatarBackgroundColor(@ColorInt int color) { - mBackgroundPaint.setColor(color); - invalidate(); - } - - @NonNull - private String extractInitial(@Nullable String letter) { - if (letter == null || letter.trim().length() <= 0) return "?"; - return String.valueOf(letter.charAt(0)); - } - - private void updateTextBounds() { - mTextPaint.getTextBounds(mInitial, 0, mInitial.length(), mTextBounds); - } -} diff --git a/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.kt b/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.kt new file mode 100644 index 0000000..4898b1d --- /dev/null +++ b/circularimageview/src/main/java/com/github/abdularis/civ/AvatarImageView.kt @@ -0,0 +1,141 @@ +package com.github.abdularis.civ + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import androidx.annotation.ColorInt +import androidx.annotation.Dimension +import androidx.annotation.IntDef + +/** + * Created by abdularis on 22/03/18. + */ +class AvatarImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : CircleImageView(context, attrs) { + @IntDef(SHOW_INITIAL, SHOW_IMAGE) + annotation class State + + private val mTextPaint: Paint + private val mTextBounds: Rect + private val mBackgroundPaint: Paint + private val mBackgroundBounds: RectF + private var initial: String + private var mText: String + private var mShowState: Int + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + updateCircleDrawBounds(mBackgroundBounds) + } + + override fun onDraw(canvas: Canvas) { + if (mShowState == SHOW_INITIAL) { + val textBottom = mBackgroundBounds.centerY() - mTextBounds.exactCenterY() + canvas.drawOval(mBackgroundBounds, mBackgroundPaint) + canvas.drawText(initial, mBackgroundBounds.centerX(), textBottom, mTextPaint) + drawStroke(canvas) + drawHighlight(canvas) + } else { + super.onDraw(canvas) + } + } + + var text: String? + get() = mText + set(text) { + mText = text ?: "" + initial = extractInitial(text) + updateTextBounds() + invalidate() + } + + @get:State + var state: Int + get() = mShowState + set(state) { + if (state != SHOW_INITIAL && state != SHOW_IMAGE) { + val msg = + "Illegal avatar state value: $state, use either SHOW_INITIAL or SHOW_IMAGE constant" + throw IllegalArgumentException(msg) + } + mShowState = state + invalidate() + } + + @get:Dimension + var textSize: Float + get() = mTextPaint.textSize + set(size) { + mTextPaint.textSize = size + updateTextBounds() + invalidate() + } + + @get:ColorInt + var textColor: Int + get() = mTextPaint.color + set(color) { + mTextPaint.color = color + invalidate() + } + + @get:ColorInt + var avatarBackgroundColor: Int + get() = mBackgroundPaint.color + set(color) { + mBackgroundPaint.color = color + invalidate() + } + + private fun extractInitial(letter: String?): String { + return if (letter == null || letter.trim { it <= ' ' }.isEmpty()) "?" else letter[0].toString() + } + + private fun updateTextBounds() { + mTextPaint.getTextBounds(initial, 0, initial.length, mTextBounds) + } + + companion object { + const val SHOW_INITIAL = 1 + const val SHOW_IMAGE = 2 + private const val DEF_INITIAL = "A" + private const val DEF_TEXT_SIZE = 90 + private const val DEF_BACKGROUND_COLOR = 0xE53935 + + @State + private val DEF_STATE = SHOW_INITIAL + } + + init { + var text = DEF_INITIAL + var textColor = Color.WHITE + var textSize = DEF_TEXT_SIZE + var backgroundColor = DEF_BACKGROUND_COLOR + var showState = DEF_STATE + attrs?.let { + val a = + context.obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0) + text = a.getString(R.styleable.AvatarImageView_text) ?: "" + textColor = a.getColor(R.styleable.AvatarImageView_textColor, textColor) + textSize = a.getDimensionPixelSize(R.styleable.AvatarImageView_textSize, textSize) + backgroundColor = + a.getColor(R.styleable.AvatarImageView_avatarBackgroundColor, backgroundColor) + showState = a.getInt(R.styleable.AvatarImageView_view_state, showState) + a.recycle() + } + mShowState = showState + mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mTextPaint.textAlign = Paint.Align.CENTER + mTextPaint.color = textColor + mTextPaint.textSize = textSize.toFloat() + mTextBounds = Rect() + mText = text + initial = extractInitial(text) + updateTextBounds() + mBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mBackgroundPaint.color = backgroundColor + mBackgroundPaint.style = Paint.Style.FILL + mBackgroundBounds = RectF() + } +} \ No newline at end of file From b4478923eaadaa4e1ac68f843a0d7dfe011b36cd Mon Sep 17 00:00:00 2001 From: Abdul Aris Date: Fri, 21 Aug 2020 09:48:09 +0700 Subject: [PATCH 5/5] update readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 838040a..d1752d3 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Circular%20Image%20View-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/6870) This library provides you circle and avatar imageview for android. it automatically scale and center a bitmap based on the size of the view but does not copy the bitmap itself. -> this project was inspired by [hdodenhof CircleImageView](https://github.com/hdodenhof/CircleImageView) Read this article: * [https://medium.com/@abdularis/android-custom-view-tutorial-create-circle-image-view-cacdd3e986cb](https://medium.com/@abdularis/android-custom-view-tutorial-create-circle-image-view-cacdd3e986cb)