diff options
Diffstat (limited to 'imagepicker')
10 files changed, 373 insertions, 41 deletions
diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle index 14c07ac..d63f5fd 100644 --- a/imagepicker/build.gradle +++ b/imagepicker/build.gradle @@ -10,6 +10,7 @@ dependencies { compile "com.github.bumptech.glide:glide:${GLIDE}" kapt "com.github.bumptech.glide:compiler:${GLIDE}" + compile "jp.wasabeef:blurry:${BLURRY}" } apply from: '../artifacts.gradle' diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt new file mode 100644 index 0000000..8fb5cf3 --- /dev/null +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/BlurredImageView.kt @@ -0,0 +1,166 @@ +package ca.allanwang.kau.imagepicker + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import ca.allanwang.kau.ui.views.MeasureSpecContract +import ca.allanwang.kau.ui.views.MeasureSpecDelegate +import ca.allanwang.kau.utils.* +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.IconicsDrawable +import jp.wasabeef.blurry.internal.BlurFactor +import jp.wasabeef.blurry.internal.BlurTask + +/** + * Created by Allan Wang on 2017-07-14. + * + * ImageView that is can be blurred and selected + * The frame is composed of three layers: the base, the blur, and the foreground + * Images should be placed in the base view, and the blur view should not be touched + * as the class will handle it + * The foreground by default contains a white checkmark, but can be customized or hidden depending on the situation + */ +class BlurredImageView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), MeasureSpecContract by MeasureSpecDelegate() { + + private var blurred = false + val imageBase: ImageView by bindView(R.id.image_base) + internal val imageBlur: ImageView by bindView(R.id.image_blur) + val imageForeground: ImageView by bindView(R.id.image_foreground) + + init { + inflate(R.layout.kau_blurred_imageview, true) + initAttrs(context, attrs) + imageForeground.setIcon(GoogleMaterial.Icon.gmd_check, 30) + } + + companion object { + const val ANIMATION_DURATION = 200L + const val ANIMATION_SCALE = 0.95f + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val result = onMeasure(this, widthMeasureSpec, heightMeasureSpec) + super.onMeasure(result.first, result.second) + } + + override fun clearAnimation() { + super.clearAnimation() + imageBase.clearAnimation() + imageBlur.clearAnimation() + imageForeground.clearAnimation() + } + + private fun View.scaleAnimate(scale: Float) = animate().scaleX(scale).scaleY(scale).setDuration(ANIMATION_DURATION) + private fun View.alphaAnimate(alpha: Float) = animate().alpha(alpha).setDuration(ANIMATION_DURATION) + + + fun isBlurred(): Boolean { + return blurred + } + + /** + * Applies a blur and fills the blur image asynchronously + * When ready, scales the image down and shows the blur & foreground + */ + fun blur() { + if (blurred) return + blurred = true + val factor = BlurFactor() + factor.width = width + factor.height = height + val task = BlurTask(imageBase, factor) { + imageBlur.setImageDrawable(it) + scaleAnimate(ANIMATION_SCALE).start() + imageBlur.alphaAnimate(1f).start() + imageForeground.alphaAnimate(1f).start() + } + task.execute() + } + + /** + * Clears animations and blurs the image without further animations + * This method is relatively instantaneous, as retrieving the blurred image + * is still asynchronous and takes time + */ + fun blurInstantly() { + blurred = true + clearAnimation() + val factor = BlurFactor() + factor.width = width + factor.height = height + BlurTask(imageBase, factor) { drawable -> + imageBlur.setImageDrawable(drawable) + scaleX = ANIMATION_SCALE + scaleY = ANIMATION_SCALE + imageBlur.alpha = 1f + imageForeground.alpha = 1f + }.execute() + } + + /** + * Animate view back to original state and remove drawable when finished + */ + fun removeBlur() { + if (!blurred) return + blurred = false + scaleAnimate(1.0f).start() + imageBlur.alphaAnimate(0f).withEndAction { imageBlur.setImageDrawable(null) }.start() + imageForeground.alphaAnimate(0f).start() + } + + + /** + * Clear all animations and unblur the image + */ + fun removeBlurInstantly() { + blurred = false + clearAnimation() + scaleX = 1.0f + scaleX = 1.0f + imageBlur.alpha = 0f + imageBlur.setImageDrawable(null) + imageForeground.alpha = 0f + } + + /** + * Switch blur state and apply transition + * + * @return true if new state is blurred; false otherwise + */ + fun toggleBlur(): Boolean { + if (blurred) removeBlur() + else blur() + return blurred + } + + /** + * Clears all of the blur effects to restore the original states + * If views were modified in other ways, this method won't affect it + */ + fun reset() { + removeBlurInstantly() + imageBase.setImageDrawable(null) + } + + /** + * Reset most of possible changes to the view + */ + fun fullReset() { + reset() + fullAction({ it.visible().background = null }) + imageForeground.setBackgroundColorRes(R.color.kau_blurred_image_selection_overlay) + imageForeground.setIcon(GoogleMaterial.Icon.gmd_check, 30, Color.WHITE) + } + + private fun fullAction(action: (View) -> Unit) { + action(this) + action(imageBase) + action(imageBlur) + action(imageForeground) + } +}
\ No newline at end of file diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt index 0b13a30..852e1e8 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageItem.kt @@ -1,18 +1,89 @@ package ca.allanwang.kau.imagepicker +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable import android.support.v7.widget.RecyclerView import android.view.View -import android.widget.ImageView import ca.allanwang.kau.iitems.KauIItem import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.gone +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon /** * Created by Allan Wang on 2017-07-04. */ -class ImageItem(data:String) - : KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_card, { ViewHolder(it) }) { +class ImageItem(val data: ImageModel) + : KauIItem<ImageItem, ImageItem.ViewHolder>(R.layout.kau_iitem_image, { ViewHolder(it) }) { + + private var failedToLoad = false + + fun bindEvents(fastAdapter: FastAdapter<ImageItem>) { + fastAdapter.withMultiSelect(true) + fastAdapter.withSelectable(true) + fastAdapter.withOnClickListener { v, _, _, _ -> + val image = v as BlurredImageView + image.toggleBlur() + true + } + } + + override fun bindView(holder: ViewHolder, payloads: List<Any>?) { + super.bindView(holder, payloads) + holder.container.alpha = 0f + Glide.with(holder.itemView) + .load(data.data) + .listener(object : RequestListener<Drawable> { + override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>, isFirstResource: Boolean): Boolean { + failedToLoad = true; + holder.container.setIcon(GoogleMaterial.Icon.gmd_error); + holder.container.animate().alpha(1f).start(); + return true; + } + + override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean { + holder.container.animate().alpha(1f).start(); + return false; + } + }) + .into(holder.container.imageBase) + } + + private fun BlurredImageView.setIcon(icon: IIcon) { + val sizePx = computeViewSize(context) + imageBase.setImageDrawable(IconicsDrawable(context, icon) + .sizePx(sizePx) + .paddingPx(sizePx / 3) + .color(Color.WHITE)) + //todo add background + imageBase.setBackgroundColor(ImagePickerActivityBase.accentColor) + imageForeground.gone() + } + + private fun computeViewSize(context: Context): Int { + val screenWidthPx = context.resources.displayMetrics.widthPixels + return screenWidthPx / ImagePickerActivityBase.computeColumnCount(context) + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + if (!failedToLoad) { + Glide.with(holder.itemView).clear(holder.container.imageBase) + holder.container.removeBlurInstantly() + } else { + holder.container.fullReset() + } + } class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val image: ImageView by bindView(R.id.kau_image) + val container: BlurredImageView by bindView(R.id.kau_image) } }
\ No newline at end of file diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt new file mode 100644 index 0000000..26e4137 --- /dev/null +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImageModel.kt @@ -0,0 +1,21 @@ +package ca.allanwang.kau.imagepicker + +import android.database.Cursor +import android.provider.MediaStore +import android.support.annotation.NonNull + + +/** + * Created by Allan Wang on 2017-07-14. + */ +class ImageModel(@NonNull cursor: Cursor) { + + val size = cursor.getLong(MediaStore.Images.Media.SIZE) + val dateModified = cursor.getLong(MediaStore.Images.Media.DATE_MODIFIED) + val data = cursor.getString(MediaStore.Images.Media.DATA) + val displayName = cursor.getString(MediaStore.Images.Media.DISPLAY_NAME) + + private fun Cursor.getString(name: String) = getString(getColumnIndex(name)) + private fun Cursor.getLong(name: String) = getLong(getColumnIndex(name)) + +}
\ No newline at end of file diff --git a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt index 8dfbeab..24c2db7 100644 --- a/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt +++ b/imagepicker/src/main/kotlin/ca/allanwang/kau/imagepicker/ImagePickerActivityBase.kt @@ -1,5 +1,7 @@ package ca.allanwang.kau.imagepicker +import android.Manifest +import android.content.Context import android.database.Cursor import android.os.Bundle import android.provider.MediaStore @@ -10,9 +12,10 @@ import android.support.v7.app.AppCompatActivity import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.RecyclerView import android.support.v7.widget.Toolbar -import ca.allanwang.kau.logging.KL +import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.ui.widgets.ElasticDragDismissFrameLayout import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.dimenPixelSize import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter @@ -27,14 +30,37 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load val recycler: RecyclerView by bindView(R.id.kau_recycler) val imageAdapter = FastItemAdapter<ImageItem>() + companion object { + /** + * Given the dimensions of our device and a minimum image size, + * Computer the optimal column count for our grid layout + * + * @return column count + */ + fun computeColumnCount(context: Context): Int { + val minImageSizePx = context.dimenPixelSize(R.dimen.kau_image_minimum_size) + val screenWidthPx = context.resources.displayMetrics.widthPixels + return screenWidthPx / minImageSizePx + } + + var accentColor: Int = 0xff666666.toInt() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.kau_activity_image_picker) - with(recycler) { - layoutManager = GridLayoutManager(this@ImagePickerActivityBase, 3) - adapter = this@ImagePickerActivityBase.imageAdapter + recycler.layoutManager = GridLayoutManager(this, computeColumnCount(this)) + recycler.adapter = imageAdapter + + with(imageAdapter) { + withPositionBasedStateManagement(false) + withMultiSelect(true) + withSelectable(true) + withOnClickListener { v, _, _, _ -> + (v as BlurredImageView).toggleBlur() + true + } } - imageAdapter.add(arrayOf("a", "b", "c").map { ImageItem(it) }) draggableFrame.addListener(object : ElasticDragDismissFrameLayout.SystemChromeFader(this) { override fun onDragDismissed() { if (draggableFrame.translationY < 0) { @@ -44,33 +70,45 @@ abstract class ImagePickerActivityBase : AppCompatActivity(), LoaderManager.Load finishAfterTransition() } }) + kauRequestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE) { + granted, _ -> + if (granted) { + supportLoaderManager.initLoader(42, null, this) + } + } } override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { val columns = arrayOf( MediaStore.Images.Media._ID, + MediaStore.Images.Media.TITLE, MediaStore.Images.Media.DATA, + MediaStore.Images.Media.SIZE, MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.DATE_MODIFIED) - - return CursorLoader(this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, null) + MediaStore.Images.Media.DATE_MODIFIED + ) + return CursorLoader(this, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + columns, + null, + null, + MediaStore.Images.Media.DATE_MODIFIED + " DESC") } - override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) { - val dataIndex = data.getColumnIndex(MediaStore.Images.Media.DATA) - val alstPhotos = mutableListOf<String>() - - data.moveToLast() - while (!data.isBeforeFirst) { - val photoPath = data.getString(dataIndex) - KL.d(photoPath) - alstPhotos.add(photoPath) - data.moveToPrevious() + override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) { + reset() + if (data == null) return + if (data.moveToFirst()) { + do { + val img = ImageModel(data) + imageAdapter.add(ImageItem(img)) + } while (data.moveToNext()) } - imageAdapter.add(alstPhotos.map { ImageItem(it) }) } - override fun onLoaderReset(loader: Loader<Cursor>) { - imageAdapter.clear() + private fun reset() { + imageAdapter.clear(); } + + override fun onLoaderReset(loader: Loader<Cursor>) = reset() }
\ No newline at end of file diff --git a/imagepicker/src/main/res/layout/kau_blurred_imageview.xml b/imagepicker/src/main/res/layout/kau_blurred_imageview.xml new file mode 100644 index 0000000..e28cb9a --- /dev/null +++ b/imagepicker/src/main/res/layout/kau_blurred_imageview.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/image_base" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/kau_blurrable_imageview" + android:scaleType="centerCrop" /> + + <ImageView + android:id="@+id/image_blur" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0" + android:contentDescription="@string/kau_blurrable_imageview" + android:scaleType="centerCrop" /> + + <ImageView + android:id="@+id/image_foreground" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:alpha="0" + android:background="@color/kau_blurred_image_selection_overlay" + android:contentDescription="@string/kau_blurrable_imageview" + android:scaleType="centerInside" /> + +</merge>
\ No newline at end of file diff --git a/imagepicker/src/main/res/layout/kau_iitem_image.xml b/imagepicker/src/main/res/layout/kau_iitem_image.xml index 22cc998..9d51d77 100644 --- a/imagepicker/src/main/res/layout/kau_iitem_image.xml +++ b/imagepicker/src/main/res/layout/kau_iitem_image.xml @@ -1,15 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> -<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" +<ca.allanwang.kau.imagepicker.BlurredImageView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/kau_image" android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/kau_image" - android:layout_width="0dp" - android:layout_height="0dp" - android:scaleType="centerCrop" - android:background="#f0f" - app:layout_constraintDimensionRatio="1:1" /> - -</android.support.constraint.ConstraintLayout>
\ No newline at end of file + android:layout_height="match_parent" + android:layout_margin="2dp" + android:foreground="@drawable/kau_selectable_white" + app:relativeHeight="1" />
\ No newline at end of file diff --git a/imagepicker/src/main/res/values/colors.xml b/imagepicker/src/main/res/values/colors.xml new file mode 100644 index 0000000..ebaa3f7 --- /dev/null +++ b/imagepicker/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <color name="kau_blurred_image_selection_overlay">#30000000</color> + +</resources>
\ No newline at end of file diff --git a/imagepicker/src/main/res/values/dimens.xml b/imagepicker/src/main/res/values/dimens.xml new file mode 100644 index 0000000..3ff2dd7 --- /dev/null +++ b/imagepicker/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <dimen name="kau_image_minimum_size">120dp</dimen> + +</resources>
\ No newline at end of file diff --git a/imagepicker/src/main/res/values/styles.xml b/imagepicker/src/main/res/values/styles.xml index e2d7280..1fbb184 100644 --- a/imagepicker/src/main/res/values/styles.xml +++ b/imagepicker/src/main/res/values/styles.xml @@ -1,8 +1,5 @@ <resources> - <style name="Kau.Translucent.ImagePicker"> - <!--<item name="android:windowEnterTransition">@transition/kau_enter_slide_bottom</item>--> - <!--<item name="android:windowReturnTransition">@transition/kau_about_return_downward</item>--> - </style> + <style name="Kau.Translucent.ImagePicker" parent="Kau.Translucent.SlideBottom" /> </resources> |