diff options
Diffstat (limited to 'imagepicker/src/main/kotlin/ca')
4 files changed, 321 insertions, 25 deletions
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 |