diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86c290e --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +### Android ### +# Built application files +*.apk +*.ap_ + +# Files for the Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +## Directory-based project format +.idea/ +# if you remove the above rule, at least ignore user-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# and these sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml + +## File-based project format +*.ipr +*.iws +*.iml + +## Additional for IntelliJ +out/ + +# generated by mpeltonen/sbt-idea plugin +.idea_modules/ + +# generated by JIRA plugin +atlassian-ide-plugin.xml \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..38fd060 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.litao.android.androidimagepicker" + minSdkVersion 14 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + vectorDrawables.useSupportLibrary = true + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':lib') + compile 'org.greenrobot:eventbus:3.0.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..901932f --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/demohour/Desktop/litao/soft/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/litao/android/androidimagepicker/ApplicationTest.java b/app/src/androidTest/java/com/litao/android/androidimagepicker/ApplicationTest.java new file mode 100644 index 0000000..fe786a5 --- /dev/null +++ b/app/src/androidTest/java/com/litao/android/androidimagepicker/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.litao.android.androidimagepicker; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d7518e1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/litao/android/androidimagepicker/ChooseAdapter.java b/app/src/main/java/com/litao/android/androidimagepicker/ChooseAdapter.java new file mode 100644 index 0000000..eaaa13b --- /dev/null +++ b/app/src/main/java/com/litao/android/androidimagepicker/ChooseAdapter.java @@ -0,0 +1,134 @@ +package com.litao.android.androidimagepicker; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.litao.android.lib.entity.PhotoEntry; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by 李涛 on 16/4/30. + */ +public class ChooseAdapter extends RecyclerView.Adapter { + + private List list = new ArrayList(); + + private Context mContext; + + private LayoutInflater mInflater; + + private OnItmeClickListener mlistener; + + public interface OnItmeClickListener{ + void onItemClicked(int position); + + } + + public ChooseAdapter(Context mContext) { + this.mContext = mContext; + mlistener = (OnItmeClickListener) mContext; + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + list.add(createAddEntry()); + } + + public void reloadList(List data) { + if (data != null) { + list.clear(); + list.addAll(data); + list.add(createAddEntry()); + } else { + list.clear(); + } + notifyDataSetChanged(); + + } + + public void appendList(List data) { + if (data != null) { + list.addAll(list.size()-1,data); + } else { + list.clear(); + } + notifyDataSetChanged(); + + } + + + public void appendPhoto(PhotoEntry entry) { + if (entry != null) { + list.add(list.size()-1,entry); + } + notifyDataSetChanged(); + } + + public List getData(){ + return list.subList(0,list.size()-1); + } + public PhotoEntry getEntry(int position) { + return list.get(position); + } + + private PhotoEntry createAddEntry(){ + return new PhotoEntry(); + } + + @Override + public int getItemViewType(int position) { + return position; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + ViewHolder vh = new ViewHolder(mInflater.inflate(R.layout.item_selected_photo, viewGroup, false), i); + return vh; + } + + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + if (i==list.size()-1){ + viewHolder.mImageView.setImageResource(R.mipmap.add); + }else { + PhotoEntry entry = list.get(i); + Glide.with(mContext) + .load(new File(entry.getPath())) + .centerCrop() + .placeholder(com.litao.android.lib.R.mipmap.default_image) + .into(viewHolder.mImageView); + } + } + + @Override + public int getItemCount() { + return list.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + private ImageView mImageView; + + private int position; + + public ViewHolder(View itemView, int position) { + super(itemView); + this.position = position; + mImageView = (ImageView) itemView.findViewById(R.id.image); + mImageView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + mlistener.onItemClicked(position); + } + } + +} diff --git a/app/src/main/java/com/litao/android/androidimagepicker/EventEntry.java b/app/src/main/java/com/litao/android/androidimagepicker/EventEntry.java new file mode 100644 index 0000000..f57d477 --- /dev/null +++ b/app/src/main/java/com/litao/android/androidimagepicker/EventEntry.java @@ -0,0 +1,24 @@ +package com.litao.android.androidimagepicker; + +import com.litao.android.lib.entity.PhotoEntry; + +import java.util.List; + +/** + * Created by 李涛 on 16/4/30. + */ +public class EventEntry { + + public static final int RECEIVED_PHOTOS_ID = 0x00000010; + + public static final int SELECTED_PHOTOS_ID = 0x00000020; + + + public List photos; + public int id; + + public EventEntry(List photos, int id){ + this.photos = photos; + this.id = id; + } +} diff --git a/app/src/main/java/com/litao/android/androidimagepicker/MainActivity.java b/app/src/main/java/com/litao/android/androidimagepicker/MainActivity.java new file mode 100644 index 0000000..5873d7d --- /dev/null +++ b/app/src/main/java/com/litao/android/androidimagepicker/MainActivity.java @@ -0,0 +1,70 @@ +package com.litao.android.androidimagepicker; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; + +import com.litao.android.lib.Utils.GridSpacingItemDecoration; +import com.litao.android.lib.entity.PhotoEntry; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class MainActivity extends AppCompatActivity implements ChooseAdapter.OnItmeClickListener { + + private RecyclerView mRecyclerView; + + private ChooseAdapter mAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + EventBus.getDefault().register(this); + + mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); + mAdapter = new ChooseAdapter(this); + mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3)); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(3, 4, true)); + + } + + @Override + protected void onDestroy() { + EventBus.getDefault().unregister(this); + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public void onItemClicked(int position) { + if (position == mAdapter.getItemCount()-1) { + startActivity(new Intent(MainActivity.this, PhotosActivity.class)); + EventBus.getDefault().postSticky(new EventEntry(mAdapter.getData(),EventEntry.SELECTED_PHOTOS_ID)); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void photosMessageEvent(EventEntry entries){ + if (entries.id == EventEntry.RECEIVED_PHOTOS_ID) { + mAdapter.reloadList(entries.photos); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void photoMessageEvent(PhotoEntry entry){ + mAdapter.appendPhoto(entry); + } + +} diff --git a/app/src/main/java/com/litao/android/androidimagepicker/PhotosActivity.java b/app/src/main/java/com/litao/android/androidimagepicker/PhotosActivity.java new file mode 100644 index 0000000..534edd3 --- /dev/null +++ b/app/src/main/java/com/litao/android/androidimagepicker/PhotosActivity.java @@ -0,0 +1,147 @@ +package com.litao.android.androidimagepicker; + +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.litao.android.lib.BaseGalleryActivity; +import com.litao.android.lib.Configuration; +import com.litao.android.lib.entity.PhotoEntry; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.List; + +/** + * Created by 李涛 on 16/4/29. + */ +public class PhotosActivity extends BaseGalleryActivity implements View.OnClickListener { + + private TextView mTextViewOpenAlbum; + private TextView mTextViewSelectedCount; + private TextView mTextViewSend; + + private List mSelectedPhotos; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_photos); + EventBus.getDefault().register(this); + setTitle("Photos"); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + initView(); + attachFragment(R.id.gallery_root); + } + + @Override + protected void onDestroy() { + EventBus.getDefault().unregister(this); + super.onDestroy(); + } + + private void initView(){ + + mTextViewOpenAlbum = (TextView) findViewById(R.id.album); + mTextViewSelectedCount = (TextView) findViewById(R.id.selected_count); + mTextViewSend = (TextView) findViewById(R.id.send_photos); + + mTextViewOpenAlbum.setOnClickListener(this); + mTextViewSelectedCount.setOnClickListener(this); + mTextViewSend.setOnClickListener(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home){ + finish(); + } + return super.onOptionsItemSelected(item); + + } + + @Override + public void onClick(View view) { + switch(view.getId()){ + case R.id.album: + openAlbum(); + break; + case R.id.send_photos: + sendPhotos(); + break; + } + } + + @Override + public Configuration getConfiguration() { + Configuration cfg=new Configuration.Builder() + .hasCamera(true) + .hasShade(true) + .hasPreview(true) + .setSpaceSize(3) + .setPhotoMaxWidth(120) + .setCheckBoxColor(0xFF3F51B5) + .setDialogHeight(Configuration.DIALOG_HALF) + .setDialogMode(Configuration.DIALOG_GRID) + .setMaximum(9) + .setTip(null) + .setAblumsTitle(null) + .build(); + return cfg; + } + + @Override + public List getSelectPhotos() { + return mSelectedPhotos; + } + + @Override + public void onSelectedCountChanged(int count) { + mTextViewSelectedCount.setVisibility(count>0?View.VISIBLE:View.INVISIBLE); + mTextViewSelectedCount.setText(String.valueOf(count)); + } + + @Override + public void onAlbumChanged(String name) { + getSupportActionBar().setSubtitle(name); + } + + @Override + public void onTakePhoto(PhotoEntry entry) { + EventBus.getDefault().post(entry); + finish(); + } + + @Override + public void onChoosePhotos(List entries) { + EventBus.getDefault().post(new EventEntry(entries,EventEntry.RECEIVED_PHOTOS_ID)); + finish(); + } + + @Override + public void onPhotoClick(PhotoEntry entry) { + + } + + @Subscribe(sticky = true,threadMode = ThreadMode.MAIN) + public void photosMessageEvent(EventEntry entry){ + if (entry.id == EventEntry.SELECTED_PHOTOS_ID) { + mSelectedPhotos = entry.photos; + } + } + + +} diff --git a/app/src/main/res/drawable/shape_round.xml b/app/src/main/res/drawable/shape_round.xml new file mode 100644 index 0000000..0499bf1 --- /dev/null +++ b/app/src/main/res/drawable/shape_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..124dd52 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/layout/activity_photos.xml b/app/src/main/res/layout/activity_photos.xml new file mode 100644 index 0000000..4e5a08f --- /dev/null +++ b/app/src/main/res/layout/activity_photos.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_selected_photo.xml b/app/src/main/res/layout/item_selected_photo.xml new file mode 100644 index 0000000..d7ddcca --- /dev/null +++ b/app/src/main/res/layout/item_selected_photo.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000..b1cb908 --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/add.png b/app/src/main/res/mipmap-xhdpi/add.png new file mode 100644 index 0000000..ef868e2 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/add.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..77c7a47 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FF424242 + #66000000 + #FFFFFFFF + #212121 + #3F51B5 + #3F51B5 + #FF4081 + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..34d1fbe --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + Android-Image-Picker + + Hello world! + Settings + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..4883d6b --- /dev/null +++ b/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.0-beta3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/gif/gallery.gif b/gif/gallery.gif new file mode 100644 index 0000000..ae883d1 Binary files /dev/null and b/gif/gallery.gif differ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1d3591c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# 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 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8c0fb64 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..fd3e60d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Apr 21 19:18:30 CST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lib/build.gradle b/lib/build.gradle new file mode 100644 index 0000000..fcd7d77 --- /dev/null +++ b/lib/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:design:23.3.0' + compile 'com.android.support:recyclerview-v7:23.3.0' + compile 'com.github.bumptech.glide:glide:3.7.0' +} diff --git a/lib/proguard-rules.pro b/lib/proguard-rules.pro new file mode 100644 index 0000000..901932f --- /dev/null +++ b/lib/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/demohour/Desktop/litao/soft/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/lib/src/androidTest/java/com/litao/android/lib/ApplicationTest.java b/lib/src/androidTest/java/com/litao/android/lib/ApplicationTest.java new file mode 100644 index 0000000..20ff5fd --- /dev/null +++ b/lib/src/androidTest/java/com/litao/android/lib/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.litao.android.lib; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fae626f --- /dev/null +++ b/lib/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/lib/src/main/java/com/litao/android/lib/BaseGalleryActivity.java b/lib/src/main/java/com/litao/android/lib/BaseGalleryActivity.java new file mode 100644 index 0000000..8c7f04b --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/BaseGalleryActivity.java @@ -0,0 +1,28 @@ +package com.litao.android.lib; + + +import android.app.FragmentManager; +import android.support.v7.app.AppCompatActivity; + +/** + * Created by 李涛 on 16/4/22. + */ +public abstract class BaseGalleryActivity extends AppCompatActivity implements GalleryFragment.OnGalleryAttachedListener { + + private GalleryFragment fragment; + + protected void attachFragment(int layoutId) { + fragment = (GalleryFragment) GalleryFragment.newInstance(); + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction().replace(layoutId, fragment).commit(); + } + + protected void openAlbum() { + fragment.openAlbum(); + } + + protected void sendPhotos() { + fragment.sendPhtots(); + } + +} diff --git a/lib/src/main/java/com/litao/android/lib/Configuration.java b/lib/src/main/java/com/litao/android/lib/Configuration.java new file mode 100644 index 0000000..4421dd7 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/Configuration.java @@ -0,0 +1,212 @@ +package com.litao.android.lib; + +/** + * Created by 李涛 on 16/4/21. + */ +public class Configuration { + + /** + * ablums dialog full screen + */ + public static final int DIALOG_FULL = -1; + + /** + * ablums dialog half screen + */ + public static final int DIALOG_HALF = -2; + + /** + * ablums dialog mode grid view + */ + public static final int DIALOG_GRID = -1; + + /** + * ablums dialog mode list view + */ + public static final int DIALOG_LIST = -2; + + /** + * Flag to indicate that this view has a camera button + */ + public boolean hasCamera; + + /** + * Flag to indicate that this photo view has a layer mask + */ + public boolean hasShade; + + /** + * Flag to indicate that this photo view clickable + */ + public boolean hasPreview; + + /** + * GridView spacing between rows and columns + */ + public int spaceSize; + + /** + * Maximum width of photos + */ + public int photoMaxWidth; + + /** + * Checkbox background color + */ + public int checkBoxColor; + + /** + * ablums dialog height + */ + public int dialogHeight; + + /** + * ablums dialog title + */ + public String ablumsTitle; + + /** + * maximum photos selection limit + */ + public int maximum; + + /** + * ablums dialog mode + * + * DIALOG_FULL or DIALOG_HALF + */ + public int dialogMode; + + /** + * Toast of maximum photos selection limit + */ + public String tip; + + private Configuration(final Builder builder) { + this.hasCamera = builder.hasCamera; + this.spaceSize = builder.spaceSize; + this.photoMaxWidth = builder.photoMaxWidth; + this.checkBoxColor = builder.checkBoxColor; + this.dialogHeight = builder.dialogHeight; + this.maximum = builder.maximum; + this.tip = builder.tip; + this.ablumsTitle = builder.ablumsTitle; + this.hasShade = builder.hasShade; + this.dialogMode = builder.dialogMode; + this.hasPreview = builder.hasPreview; + } + + public static class Builder { + + private boolean hasCamera; + + private boolean hasShade; + + private boolean hasPreview; + + private int spaceSize; + + private int photoMaxWidth; + + private int checkBoxColor; + + private int dialogHeight; + + private int dialogMode; + + public String ablumsTitle; + + private int maximum; + + private String tip; + + public Builder() { + hasCamera = true; + hasShade = true; + hasPreview = true; + spaceSize = 4; + photoMaxWidth = 120; + checkBoxColor = 0xFF3F51B5; + dialogHeight = DIALOG_HALF; + dialogMode = DIALOG_GRID; + maximum = 9; + tip = null; + ablumsTitle = null; + } + + public Builder(final Configuration cfg) { + hasCamera = cfg.hasCamera; + hasShade = cfg.hasShade; + hasPreview = cfg.hasPreview; + spaceSize = cfg.spaceSize; + photoMaxWidth = cfg.photoMaxWidth; + checkBoxColor = cfg.checkBoxColor; + dialogHeight = cfg.dialogHeight; + dialogMode = cfg.dialogMode; + maximum = cfg.maximum; + tip = cfg.tip; + ablumsTitle = cfg.ablumsTitle; + + } + + public Builder hasCamera(boolean hasCamera) { + this.hasCamera = hasCamera; + return this; + } + + public Builder hasShade(boolean hasShade){ + this.hasShade = hasShade; + return this; + } + + public Builder hasPreview(boolean hasPreview){ + this.hasPreview = hasPreview; + return this; + } + + public Builder setSpaceSize(int spaceSize) { + this.spaceSize = spaceSize; + return this; + } + + public Builder setPhotoMaxWidth(int photoMaxWidth) { + this.photoMaxWidth = photoMaxWidth; + return this; + } + + public Builder setCheckBoxColor(int checkBoxColor) { + this.checkBoxColor = checkBoxColor; + return this; + } + + + public Builder setDialogHeight(int dialogHeight) { + this.dialogHeight = dialogHeight; + return this; + } + + public Builder setDialogMode(int dialogMode){ + this.dialogMode = dialogMode; + return this; + } + + public Builder setAblumsTitle(String ablumsTitle) { + this.ablumsTitle = ablumsTitle; + return this; + } + + public Builder setMaximum(int maximum) { + this.maximum = maximum; + return this; + } + + public Builder setTip(String maximumMsg) { + this.tip = tip; + return this; + } + + public Configuration build() { + return new Configuration(this); + } + } +} diff --git a/lib/src/main/java/com/litao/android/lib/GalleryFragment.java b/lib/src/main/java/com/litao/android/lib/GalleryFragment.java new file mode 100644 index 0000000..b05f829 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/GalleryFragment.java @@ -0,0 +1,310 @@ +package com.litao.android.lib; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialog; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import com.litao.android.lib.Utils.GridSpacingItemDecoration; +import com.litao.android.lib.Utils.Utils; +import com.litao.android.lib.Utils.VerticalSpaceItemDecoration; +import com.litao.android.lib.adapter.AlbumsGridAdapter; +import com.litao.android.lib.adapter.AlbumsListAdapter; +import com.litao.android.lib.adapter.PhotosAdapter; +import com.litao.android.lib.controller.MediaController; +import com.litao.android.lib.entity.AlbumEntry; +import com.litao.android.lib.entity.PhotoEntry; +import com.litao.android.lib.view.MDCheckBox; +import com.litao.android.lib.view.SquaredView; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by 李涛 on 16/4/21. + */ +public class GalleryFragment extends Fragment implements MediaController.OnViewClickListener, MediaController.OnDataLoadListener { + + private final String TAG = "GalleryFragment"; + + /** + * take photo request code + */ + private static final int REQUEST_TAKE_PHOTO = 0x00001024; + + /** + * take photo id + */ + private static final int TAKE_PHOTO_ID = Integer.MAX_VALUE; + + private int mPhotoColumnNumber; + + private int mAlbumColumnNumber; + + private PhotosAdapter mAdapterPhoto; + + private AlbumsGridAdapter mAdapterAlbumGrid; + + private AlbumsListAdapter mAdapterAlbumList; + + private Configuration mConfig; + + private MediaController mMediaController; + + private List albumsSorted; + + private List selectedPhotos; + + private BottomSheetDialog dialog; + + private BottomSheetBehavior mBehavior; + + private File mTmpFile; + + private OnGalleryAttachedListener mListener; + + public interface OnGalleryAttachedListener { + Configuration getConfiguration(); + + List getSelectPhotos(); + + void onSelectedCountChanged(int count); + + void onAlbumChanged(String name); + + void onTakePhoto(PhotoEntry entry); + + void onChoosePhotos(List entries); + + void onPhotoClick(PhotoEntry entry); + } + + public static Fragment newInstance() { + return new GalleryFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mConfig = mListener.getConfiguration(); + + mAdapterPhoto = new PhotosAdapter(getActivity(), this, mConfig); + + if (mConfig.dialogMode >= Configuration.DIALOG_GRID) { + mAdapterAlbumGrid = new AlbumsGridAdapter(getActivity(), this); + }else { + mAdapterAlbumList = new AlbumsListAdapter(getActivity(),this); + } + + selectedPhotos = new ArrayList(); + mMediaController = new MediaController(this, getActivity()); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mPhotoColumnNumber = getGallerWidth(container) / dp2px(mConfig.photoMaxWidth); + mAlbumColumnNumber = getGallerWidth(container) / dp2px(mConfig.photoMaxWidth * 1.5f); + return inflater.inflate(R.layout.layout_photos, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + RecyclerView mGalleryView = (RecyclerView) view.findViewById(R.id.recycler_view); + mGalleryView.setLayoutManager(new GridLayoutManager(getActivity(), mPhotoColumnNumber)); + mGalleryView.setAdapter(mAdapterPhoto); + mGalleryView.addItemDecoration(new GridSpacingItemDecoration(mPhotoColumnNumber, dp2px(mConfig.spaceSize), true)); + + + mMediaController.loadGalleryPhotos(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mListener = (OnGalleryAttachedListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnGalleryAttachedListener"); + } + } + + + public void openAlbum() { + int screenHeight = Utils.getScreenHeight(getActivity()); + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + return; + } + dialog = new BottomSheetDialog(getActivity()); + + View view = LayoutInflater.from(getActivity()).inflate(R.layout.layout_albums, null); + + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, screenHeight); + view.setLayoutParams(lp); + + TextView mTextViewTitle = (TextView) view.findViewById(R.id.title); + mTextViewTitle.setText(mConfig.ablumsTitle == null ? getString(R.string.album) : mConfig.ablumsTitle); + + RecyclerView mGalleryView = (RecyclerView) view.findViewById(R.id.recycler_view); + + if (mConfig.dialogMode >= Configuration.DIALOG_GRID) { + mGalleryView.setLayoutManager(new GridLayoutManager(getActivity(), mAlbumColumnNumber)); + mGalleryView.addItemDecoration(new GridSpacingItemDecoration(mAlbumColumnNumber, dp2px(mConfig.spaceSize), true)); + mGalleryView.setItemAnimator(new DefaultItemAnimator()); + mGalleryView.setAdapter(mAdapterAlbumGrid); + mAdapterAlbumGrid.appendList(albumsSorted); + }else { + mGalleryView.setLayoutManager(new LinearLayoutManager(getActivity())); + mGalleryView.addItemDecoration(new VerticalSpaceItemDecoration(dp2px(mConfig.spaceSize))); + mGalleryView.setItemAnimator(new DefaultItemAnimator()); + mGalleryView.setAdapter(mAdapterAlbumList); + mAdapterAlbumList.appendList(albumsSorted); + } + + + dialog.setContentView(view); + mBehavior = BottomSheetBehavior.from((View) view.getParent()); + + if (mConfig.dialogHeight < 0) { + mBehavior.setPeekHeight(mConfig.dialogHeight <= Configuration.DIALOG_HALF ? screenHeight / 2 : screenHeight); + } else { + mBehavior.setPeekHeight(mConfig.dialogHeight >= screenHeight ? screenHeight : mConfig.dialogHeight); + } + dialog.show(); + } + + public void openCamera() { + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (cameraIntent.resolveActivity(getActivity().getPackageManager()) != null) { + mTmpFile = Utils.createTmpFile(getActivity()); + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTmpFile)); + this.startActivityForResult(cameraIntent, REQUEST_TAKE_PHOTO); + } else { + Log.e(TAG,"No camera is found"); + } + } + + public void sendPhtots() { + mListener.onChoosePhotos(selectedPhotos); + } + + public void togglePhoto(int position, MDCheckBox mCheckBox, SquaredView viewShade){ + if (mConfig.maximum <= selectedPhotos.size() && !mCheckBox.isChecked()) { + Toast.makeText(getActivity(), mConfig.tip == null ? String.format(getString(R.string.select_maximum),mConfig.maximum) : mConfig.tip, Toast.LENGTH_SHORT).show(); + } else { + PhotoEntry photoEntry = mAdapterPhoto.getEntry(position); + mCheckBox.toggle(); + viewShade.toggle(); + photoEntry.toggle(); + if (mCheckBox.isChecked()) { + selectedPhotos.add(photoEntry); + } else { + selectedPhotos.remove(photoEntry); + } + mListener.onSelectedCountChanged(selectedPhotos.size()); + } + } + + private void updateCheckstatus() { + List allEntries = albumsSorted.get(0).getPhotos(); + if (mListener.getSelectPhotos() != null) { + for (PhotoEntry entry1 : mListener.getSelectPhotos()) { + for (PhotoEntry entry2 : allEntries) { + if (TextUtils.equals(entry2.getPath(),entry1.getPath())) { + entry2.setChecked(true); + selectedPhotos.add(entry2); + break; + } + } + } + } + mListener.onSelectedCountChanged(selectedPhotos.size()); + mAdapterPhoto.notifyDataSetChanged(); + } + + + private int dp2px(float dp) { + return (int) (dp * getActivity().getResources().getDisplayMetrics().density + 0.5f); + } + + private int getGallerWidth(ViewGroup container) { + return Utils.getScreenWidth(getActivity()) - container.getPaddingLeft() - container.getPaddingRight(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_TAKE_PHOTO) { + if (resultCode == Activity.RESULT_OK) { + PhotoEntry entry = new PhotoEntry(); + entry.setImageId(TAKE_PHOTO_ID); + entry.setPath(mTmpFile.getAbsolutePath()); + Utils.addMediaToGallery(getActivity().getApplicationContext(), Uri.fromFile(new File(mTmpFile.getAbsolutePath()))); + mListener.onTakePhoto(entry); + } + } + } + + @Override + public void onPhotoClicked(int position, MDCheckBox mCheckBox, SquaredView viewShade) { + if (mConfig.hasPreview) { + mListener.onPhotoClick(mAdapterPhoto.getEntry(position)); + }else { + togglePhoto(position,mCheckBox,viewShade); + } + } + + @Override + public void onAlbumClicked(int position, View imageView) { + mListener.onAlbumChanged(albumsSorted.get(position).getBucketName()); + mAdapterPhoto.reloadList(albumsSorted.get(position).getPhotos()); + dialog.dismiss(); + } + + @Override + public void onCheckBoxClicked(int position, MDCheckBox mCheckBox,SquaredView viewShade) { + togglePhoto(position,mCheckBox,viewShade); + } + + @Override + public void onCameraClicked() { + if (mConfig.maximum > selectedPhotos.size()) { + openCamera(); + } else { + Toast.makeText(getActivity(), mConfig.tip == null ? String.format(getString(R.string.select_maximum),mConfig.maximum) : mConfig.tip, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoadFinished(List data) { + if (data != null && data.size()>0) { + albumsSorted = data; + mListener.onAlbumChanged(data.get(0).getBucketName()); + mAdapterPhoto.reloadList(data.get(0).getPhotos()); + updateCheckstatus(); + }else { + mAdapterPhoto.addCamera(); + } + } + +} diff --git a/lib/src/main/java/com/litao/android/lib/Utils/GridSpacingItemDecoration.java b/lib/src/main/java/com/litao/android/lib/Utils/GridSpacingItemDecoration.java new file mode 100644 index 0000000..e4cefd1 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/Utils/GridSpacingItemDecoration.java @@ -0,0 +1,45 @@ +package com.litao.android.lib.Utils; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by 李涛 on 16/4/22. + * + * http://stackoverflow.com/questions/28531996/android-recyclerview-gridlayoutmanager-column-spacing/30701422#30701422 + */ +public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { + + private int spanCount; + private int spacing; + private boolean includeEdge; + + public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { + this.spanCount = spanCount; + this.spacing = spacing; + this.includeEdge = includeEdge; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); // item position + int column = position % spanCount; // item column + + if (includeEdge) { + outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) + outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) + + if (position < spanCount) { // top edge + outRect.top = spacing; + } + outRect.bottom = spacing; // item bottom + } else { + outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) + outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) + if (position >= spanCount) { + outRect.top = spacing; // item top + } + } + } +} diff --git a/lib/src/main/java/com/litao/android/lib/Utils/Utils.java b/lib/src/main/java/com/litao/android/lib/Utils/Utils.java new file mode 100644 index 0000000..3cf7fee --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/Utils/Utils.java @@ -0,0 +1,56 @@ +package com.litao.android.lib.Utils; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.net.Uri; +import android.os.Environment; +import android.view.Display; + +import java.io.File; +import java.util.UUID; + +/** + * Created by 李涛 on 16/4/28. + */ +public class Utils { + private static Point getScreenSize(Context activity) { + Display display = ((Activity) activity).getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + return size; + } + + public static int getScreenWidth(Context activity) { + return getScreenSize(activity).x; + } + + public static int getScreenHeight(Context activity) { + return getScreenSize(activity).y; + } + + public static File createTmpFile(Context context) { + + String state = Environment.getExternalStorageState(); + if (state.equals(Environment.MEDIA_MOUNTED)) { + File pic = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + String fileName = UUID.randomUUID().toString(); + return new File(pic, fileName + ".jpg"); + } else { + File cacheDir = context.getCacheDir(); + String fileName = UUID.randomUUID().toString(); + return new File(cacheDir, fileName + ".jpg"); + } + + } + + public static void addMediaToGallery(Context context,Uri uri) { + if (uri == null) { + return; + } + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + mediaScanIntent.setData(uri); + context.sendBroadcast(mediaScanIntent); + } +} diff --git a/lib/src/main/java/com/litao/android/lib/Utils/VerticalSpaceItemDecoration.java b/lib/src/main/java/com/litao/android/lib/Utils/VerticalSpaceItemDecoration.java new file mode 100644 index 0000000..a5caf98 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/Utils/VerticalSpaceItemDecoration.java @@ -0,0 +1,25 @@ +package com.litao.android.lib.Utils; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by 李涛 on 16/5/2. + */ +public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration { + + private final int mVerticalSpaceHeight; + + public VerticalSpaceItemDecoration(int mVerticalSpaceHeight) { + this.mVerticalSpaceHeight = mVerticalSpaceHeight; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) { + outRect.bottom = mVerticalSpaceHeight; + } + } +} diff --git a/lib/src/main/java/com/litao/android/lib/adapter/AlbumsGridAdapter.java b/lib/src/main/java/com/litao/android/lib/adapter/AlbumsGridAdapter.java new file mode 100644 index 0000000..1469b8c --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/adapter/AlbumsGridAdapter.java @@ -0,0 +1,111 @@ +package com.litao.android.lib.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.litao.android.lib.R; +import com.litao.android.lib.controller.MediaController; +import com.litao.android.lib.entity.AlbumEntry; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by 李涛 on 16/4/25. + */ +public class AlbumsGridAdapter extends RecyclerView.Adapter { + + private List list = new ArrayList(); + + private Context mContext; + + public MediaController.OnViewClickListener mClickListener; + + public AlbumsGridAdapter(Context mContext, MediaController.OnViewClickListener mClickListener) { + this.mClickListener = mClickListener; + this.mContext = mContext; + } + + public void appendList(List data) { + if (data != null) { + list.clear(); + list.addAll(data); + } else { + list.clear(); + } + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + return position; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + ViewHolder vh = new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_grid_album, null), mClickListener, i); + return vh; + } + + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + AlbumEntry entry = list.get(i); + + viewHolder.mTextViewName.setText(entry.getBucketName()); + viewHolder.mTextViewCount.setText(String.valueOf(entry.getPhotos().size())); + + Glide.with(mContext) + .load(new File(entry.getCoverPhoto().getPath())) + .centerCrop() + .placeholder(R.mipmap.default_image) + .into(viewHolder.mImageView); + } + + @Override + public int getItemCount() { + return list.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private RelativeLayout mLayout; + + private ImageView mImageView; + + private TextView mTextViewName; + + private TextView mTextViewCount; + + public MediaController.OnViewClickListener mListener; + + private int position; + + public ViewHolder(View itemView, MediaController.OnViewClickListener mListener, int position) { + super(itemView); + + this.position = position; + this.mListener = mListener; + + mLayout = (RelativeLayout) itemView.findViewById(R.id.grid_item); + mImageView = (ImageView) itemView.findViewById(R.id.image); + mTextViewName = (TextView) itemView.findViewById(R.id.name); + mTextViewCount = (TextView) itemView.findViewById(R.id.count); + + mLayout.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + mListener.onAlbumClicked(position,view); + } + } +} diff --git a/lib/src/main/java/com/litao/android/lib/adapter/AlbumsListAdapter.java b/lib/src/main/java/com/litao/android/lib/adapter/AlbumsListAdapter.java new file mode 100644 index 0000000..5aeb969 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/adapter/AlbumsListAdapter.java @@ -0,0 +1,111 @@ +package com.litao.android.lib.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.litao.android.lib.R; +import com.litao.android.lib.controller.MediaController; +import com.litao.android.lib.entity.AlbumEntry; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by 李涛 on 16/4/25. + */ +public class AlbumsListAdapter extends RecyclerView.Adapter { + + private List list = new ArrayList(); + + private Context mContext; + + public MediaController.OnViewClickListener mClickListener; + + public AlbumsListAdapter(Context mContext, MediaController.OnViewClickListener mClickListener) { + this.mClickListener = mClickListener; + this.mContext = mContext; + } + + public void appendList(List data) { + if (data != null) { + list.clear(); + list.addAll(data); + } else { + list.clear(); + } + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + return position; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + ViewHolder vh = new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_list_album, null), mClickListener, i); + return vh; + } + + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + AlbumEntry entry = list.get(i); + + viewHolder.mTextViewName.setText(entry.getBucketName()); + viewHolder.mTextViewCount.setText(String.valueOf(entry.getPhotos().size())); + + Glide.with(mContext) + .load(new File(entry.getCoverPhoto().getPath())) + .centerCrop() + .placeholder(R.mipmap.default_image) + .into(viewHolder.mImageView); + } + + @Override + public int getItemCount() { + return list.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private RelativeLayout mLayout; + + private ImageView mImageView; + + private TextView mTextViewName; + + private TextView mTextViewCount; + + public MediaController.OnViewClickListener mListener; + + private int position; + + public ViewHolder(View itemView, MediaController.OnViewClickListener mListener, int position) { + super(itemView); + + this.position = position; + this.mListener = mListener; + + mLayout = (RelativeLayout) itemView.findViewById(R.id.list_item); + mImageView = (ImageView) itemView.findViewById(R.id.image); + mTextViewName = (TextView) itemView.findViewById(R.id.name); + mTextViewCount = (TextView) itemView.findViewById(R.id.count); + + mLayout.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + mListener.onAlbumClicked(position, view); + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/litao/android/lib/adapter/PhotosAdapter.java b/lib/src/main/java/com/litao/android/lib/adapter/PhotosAdapter.java new file mode 100644 index 0000000..c69196c --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/adapter/PhotosAdapter.java @@ -0,0 +1,176 @@ +package com.litao.android.lib.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.litao.android.lib.Configuration; +import com.litao.android.lib.R; +import com.litao.android.lib.controller.MediaController; +import com.litao.android.lib.entity.PhotoEntry; +import com.litao.android.lib.view.MDCheckBox; +import com.litao.android.lib.view.SquaredView; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by 李涛 on 16/4/21. + */ +public class PhotosAdapter extends RecyclerView.Adapter { + + private List list = new ArrayList(); + + private Context mContext; + + private LayoutInflater mInflater; + + private Configuration mConfig; + + public MediaController.OnViewClickListener mClickListener; + + public PhotosAdapter(Context mContext, MediaController.OnViewClickListener mClickListener, Configuration mConfig) { + this.mClickListener = mClickListener; + this.mContext = mContext; + this.mConfig = mConfig; + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public void reloadList(List data) { + if (data != null) { + list.clear(); + if (mConfig.hasCamera) { + PhotoEntry cameraEntry = new PhotoEntry(); + list.add(cameraEntry); + } + list.addAll(data); + } else { + list.clear(); + } + notifyDataSetChanged(); + } + + public void appendList(List data) { + if (data != null) { + if (mConfig.hasCamera) { + PhotoEntry cameraEntry = new PhotoEntry(); + list.add(cameraEntry); + } + list.addAll(data); + } else { + list.clear(); + } + notifyDataSetChanged(); + } + + public void appendPhoto(PhotoEntry entry) { + if (entry != null) { + list.add(entry); + } + notifyDataSetChanged(); + } + + public void addCamera(){ + if (mConfig.hasCamera) { + PhotoEntry cameraEntry = new PhotoEntry(); + list.add(cameraEntry); + } + notifyDataSetChanged(); + } + + public PhotoEntry getEntry(int position) { + return list.get(mConfig.hasCamera ? position + 1 : position); + } + + @Override + public int getItemViewType(int position) { + return position; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + ViewHolder vh = new ViewHolder(mInflater.inflate(R.layout.item_photo, viewGroup, false), mClickListener, i, mConfig.hasCamera); + return vh; + } + + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + + if (mConfig.hasCamera && i == 0) { + viewHolder.mImageView.setImageResource(R.mipmap.camera); + viewHolder.mCheckBox.setVisibility(View.INVISIBLE); + + } else { + PhotoEntry entry = list.get(i); + viewHolder.mCheckBox.setChecked(entry.isChecked()); + viewHolder.mCheckBox.setVisibility(View.VISIBLE); + if (mConfig.hasShade) { + viewHolder.mViewShade.setVisibility(entry.isChecked() ? View.VISIBLE : View.INVISIBLE); + } + + Glide.with(mContext) + .load(new File(entry.getPath())) + .centerCrop() + .placeholder(R.mipmap.default_image) + .into(viewHolder.mImageView); + + } + viewHolder.mCheckBox.setCheckBoxColor(mConfig.checkBoxColor); + } + + @Override + public int getItemCount() { + return list.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private ImageView mImageView; + + private MDCheckBox mCheckBox; + + private SquaredView mViewShade; + + public MediaController.OnViewClickListener mListener; + + private int position; + + private boolean hasCamera; + + public ViewHolder(View itemView, MediaController.OnViewClickListener mListener, int position, boolean hasCamera) { + + super(itemView); + + this.position = position; + this.hasCamera = hasCamera; + this.mListener = mListener; + + mImageView = (ImageView) itemView.findViewById(R.id.image); + mCheckBox = (MDCheckBox) itemView.findViewById(R.id.checkbox); + mViewShade = (SquaredView) itemView.findViewById(R.id.shade); + mImageView.setOnClickListener(this); + mCheckBox.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + if (mListener == null) return; + + if (view instanceof ImageView) { + if (position == 0 && hasCamera) { + mListener.onCameraClicked(); + }else { + mListener.onPhotoClicked(hasCamera ? position - 1 : position, mCheckBox, mViewShade); + } + } else if (view instanceof MDCheckBox) { + mListener.onCheckBoxClicked(hasCamera ? position - 1 : position, mCheckBox, mViewShade); + } + } + } +} diff --git a/lib/src/main/java/com/litao/android/lib/controller/MediaController.java b/lib/src/main/java/com/litao/android/lib/controller/MediaController.java new file mode 100644 index 0000000..fecdaa9 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/controller/MediaController.java @@ -0,0 +1,132 @@ +package com.litao.android.lib.controller; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.provider.MediaStore; +import android.view.View; + +import com.litao.android.lib.entity.AlbumEntry; +import com.litao.android.lib.entity.PhotoEntry; +import com.litao.android.lib.view.MDCheckBox; +import com.litao.android.lib.view.SquaredView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Created by 李涛 on 16/4/22. + */ +public class MediaController { + private String TAG = "MediaController"; + + private OnDataLoadListener listener; + + private Activity mContext; + + private static final String[] projectionPhotos = { + MediaStore.Images.Media._ID, + MediaStore.Images.Media.BUCKET_ID, + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, + MediaStore.Images.Media.DATA, + MediaStore.Images.Media.DATE_TAKEN + }; + + public interface OnDataLoadListener { + void onLoadFinished(List data); + } + + public interface OnViewClickListener { + void onPhotoClicked(int position, MDCheckBox checkBox, SquaredView viewShade); + + void onAlbumClicked(int position, View imageView); + + void onCheckBoxClicked(int position, MDCheckBox checkBox, SquaredView viewShade); + + void onCameraClicked(); + + } + + + public MediaController(OnDataLoadListener listener, Activity mContext) { + this.listener = listener; + this.mContext = mContext; + } + + public void loadGalleryPhotos() { + new Thread(new Runnable() { + @Override + public void run() { + final ArrayList albumsSorted = new ArrayList<>(); + HashMap albums = new HashMap<>(); + AlbumEntry allPhotosAlbum = null; + String cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/"; + + Cursor cursor = null; + + try { + cursor = MediaStore.Images.Media.query(mContext.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projectionPhotos, "", null, MediaStore.Images.Media.DATE_TAKEN + " DESC"); + if (cursor != null) { + int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID); + int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + int dateColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN); + + while (cursor.moveToNext()) { + int imageId = cursor.getInt(imageIdColumn); + int bucketId = cursor.getInt(bucketIdColumn); + String bucketName = cursor.getString(bucketNameColumn); + String path = cursor.getString(dataColumn); + long dateTaken = cursor.getLong(dateColumn); + + if (path == null || path.length() == 0) { + continue; + } + + PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path); + + if (allPhotosAlbum == null) { + allPhotosAlbum = new AlbumEntry(0, "All Photos", photoEntry); + albumsSorted.add(0, allPhotosAlbum); + } + + allPhotosAlbum.addPhoto(photoEntry); + + AlbumEntry albumEntry = albums.get(bucketId); + if (albumEntry == null) { + albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry); + albums.put(bucketId, albumEntry); + + albumsSorted.add(albumEntry); + } + + albumEntry.addPhoto(photoEntry); + } + mContext.runOnUiThread(new Runnable() { + @Override + public void run() { + listener.onLoadFinished(albumsSorted); + } + }); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + try { + cursor.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + } + }).start(); + } + + +} diff --git a/lib/src/main/java/com/litao/android/lib/entity/AlbumEntry.java b/lib/src/main/java/com/litao/android/lib/entity/AlbumEntry.java new file mode 100644 index 0000000..ae09a30 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/entity/AlbumEntry.java @@ -0,0 +1,68 @@ +package com.litao.android.lib.entity; + +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by 李涛 on 16/4/22. + */ +public class AlbumEntry { + private int bucketId; + private String bucketName; + private PhotoEntry coverPhoto; + private SparseArray photoById = new SparseArray(); + private List photos = new ArrayList(); + + public AlbumEntry(int bucketId, String bucketName, PhotoEntry coverPhoto) { + this.bucketId = bucketId; + this.bucketName = bucketName; + this.coverPhoto = coverPhoto; + } + + public void addPhoto(PhotoEntry photoEntry) { + photoById.put(photoEntry.getImageId(), photoEntry); + photos.add(photoEntry); + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public int getBucketId() { + return bucketId; + } + + public void setBucketId(int bucketId) { + this.bucketId = bucketId; + } + + public PhotoEntry getCoverPhoto() { + return coverPhoto; + } + + public void setCoverPhoto(PhotoEntry coverPhoto) { + this.coverPhoto = coverPhoto; + } + + public SparseArray getPhotoById() { + return photoById; + } + + public void setPhotoById(SparseArray photoById) { + this.photoById = photoById; + } + + public List getPhotos() { + return photos; + } + + public void setPhotos(List photos) { + this.photos = photos; + } +} diff --git a/lib/src/main/java/com/litao/android/lib/entity/PhotoEntry.java b/lib/src/main/java/com/litao/android/lib/entity/PhotoEntry.java new file mode 100644 index 0000000..f9d17f7 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/entity/PhotoEntry.java @@ -0,0 +1,68 @@ +package com.litao.android.lib.entity; + +/** + * Created by 李涛 on 16/4/21. + */ +public class PhotoEntry { + + private int bucketId; + private int imageId; + private long dateTaken; + private String path; + private boolean isChecked; + + public PhotoEntry() { + + } + + public PhotoEntry(int bucketId, int imageId, long dateTaken, String path) { + this.bucketId = bucketId; + this.imageId = imageId; + this.dateTaken = dateTaken; + this.path = path; + } + + public void toggle() { + isChecked = !isChecked; + } + + public int getBucketId() { + return bucketId; + } + + public void setBucketId(int bucketId) { + this.bucketId = bucketId; + } + + public int getImageId() { + return imageId; + } + + public void setImageId(int imageId) { + this.imageId = imageId; + } + + public long getDateTaken() { + return dateTaken; + } + + public void setDateTaken(long dateTaken) { + this.dateTaken = dateTaken; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public boolean isChecked() { + return isChecked; + } + + public void setChecked(boolean checked) { + isChecked = checked; + } +} diff --git a/lib/src/main/java/com/litao/android/lib/view/MDCheckBox.java b/lib/src/main/java/com/litao/android/lib/view/MDCheckBox.java new file mode 100644 index 0000000..b3036d2 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/view/MDCheckBox.java @@ -0,0 +1,234 @@ +package com.litao.android.lib.view; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Checkable; + +import com.litao.android.lib.R; + +/** + * Created by 李涛 on 16/4/25. + */ +public class MDCheckBox extends View implements Checkable { + + private final static float BOUNCE_VALUE = 0.2f; + + private Drawable checkDrawable; + + private Paint bitmapPaint; + private Paint bitmapEraser; + private Paint checkEraser; + private Paint borderPaint; + private Paint uncheckPaint; + + private Bitmap drawBitmap; + private Bitmap checkBitmap; + private Canvas bitmapCanvas; + private Canvas checkCanvas; + + private float progress; + private ObjectAnimator checkAnim; + + private boolean attachedToWindow; + private boolean isChecked; + + private int size = 26; + private int bitmapColor = 0xFF3F51B5; + private int borderColor = 0xFFFFFFFF; + private int uncheckColor = 0x22000000; + + public MDCheckBox(Context context) { + this(context, null); + } + + public MDCheckBox(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public MDCheckBox(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapEraser = new Paint(Paint.ANTI_ALIAS_FLAG); + uncheckPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapEraser.setColor(0); + bitmapEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + checkEraser = new Paint(Paint.ANTI_ALIAS_FLAG); + checkEraser.setColor(0); + checkEraser.setStyle(Paint.Style.STROKE); + checkEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + borderPaint.setStyle(Paint.Style.STROKE); + borderPaint.setStrokeWidth(dp(2)); + checkDrawable = context.getResources().getDrawable(R.mipmap.check); + setVisibility(VISIBLE); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility == VISIBLE && drawBitmap == null) { + drawBitmap = Bitmap.createBitmap(dp(size), dp(size), Bitmap.Config.ARGB_8888); + bitmapCanvas = new Canvas(drawBitmap); + checkBitmap = Bitmap.createBitmap(dp(size), dp(size), Bitmap.Config.ARGB_8888); + checkCanvas = new Canvas(checkBitmap); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int newSpec = MeasureSpec.makeMeasureSpec(dp(size), MeasureSpec.getMode(Math.min(widthMeasureSpec, heightMeasureSpec))); + super.onMeasure(newSpec, newSpec); + } + + @Override + protected void onDraw(Canvas canvas) { + if (getVisibility() != VISIBLE) { + return; + } + checkEraser.setStrokeWidth(dp(size)); + + drawBitmap.eraseColor(0); + float rad = getMeasuredWidth() / 2; + + float bitmapProgress = progress >= 0.5f ? 1.0f : progress / 0.5f; + float checkProgress = progress < 0.5f ? 0.0f : (progress - 0.5f) / 0.5f; + + float p = isChecked ? progress : (1.0f - progress); + + if (p < BOUNCE_VALUE) { + rad -= dp(2) * p; + } else if (p < BOUNCE_VALUE * 2) { + rad -= dp(2) - dp(2) * p; + } + + borderPaint.setColor(borderColor); + uncheckPaint.setColor(uncheckColor); + canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad - dp(1), borderPaint); + canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad - dp(2), uncheckPaint); + + bitmapPaint.setColor(bitmapColor); + + bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad, bitmapPaint); + bitmapCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - bitmapProgress), bitmapEraser); + canvas.drawBitmap(drawBitmap, 0, 0, null); + + checkBitmap.eraseColor(0); + int w = checkDrawable.getIntrinsicWidth(); + int h = checkDrawable.getIntrinsicHeight(); + int x = (getMeasuredWidth() - w) / 2; + int y = (getMeasuredHeight() - h) / 2; + + checkDrawable.setBounds(x, y, x + w, y + h); + checkDrawable.draw(checkCanvas); + checkCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, rad * (1 - checkProgress), checkEraser); + + canvas.drawBitmap(checkBitmap, 0, 0, null); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attachedToWindow = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attachedToWindow = false; + } + + + public void setProgress(float value) { + if (progress == value) { + return; + } + progress = value; + invalidate(); + } + + public void setCheckBoxColor(int bitmapColor) { + this.bitmapColor = bitmapColor; + } + + public void setCheckBoxSize(int size) { + this.size = size; + } + + public float getProgress() { + return progress; + } + + public void setCheckedColor(int value) { + bitmapColor = value; + } + + public void setBorderColor(int value) { + borderColor = value; + borderPaint.setColor(borderColor); + } + + private void cancelAnim() { + if (checkAnim != null) { + checkAnim.cancel(); + } + } + + private void addAnim(boolean isChecked) { + checkAnim = ObjectAnimator.ofFloat(this, "progress", isChecked ? 1.0f : 0.0f); + checkAnim.setDuration(300); + checkAnim.start(); + } + + public void setChecked(boolean checked, boolean animated) { + if (checked == isChecked) { + return; + } + isChecked = checked; + + if (attachedToWindow && animated) { + addAnim(checked); + } else { + cancelAnim(); + setProgress(checked ? 1.0f : 0.0f); + } + } + + public void toggle(boolean animated) { + setChecked(!isChecked, animated); + } + + @Override + public void toggle() { + setChecked(!isChecked); + } + + @Override + public void setChecked(boolean b) { + setChecked(b, true); + } + + @Override + public boolean isChecked() { + return isChecked; + } + + public int dp(float value) { + if (value == 0) { + return 0; + } + float density = getContext().getResources().getDisplayMetrics().density; + return (int) Math.ceil(density * value); + } +} diff --git a/lib/src/main/java/com/litao/android/lib/view/SquaredImageView.java b/lib/src/main/java/com/litao/android/lib/view/SquaredImageView.java new file mode 100644 index 0000000..b66035e --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/view/SquaredImageView.java @@ -0,0 +1,24 @@ +package com.litao.android.lib.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * Created by 李涛 on 16/4/21. + */ +public class SquaredImageView extends ImageView { + public SquaredImageView(Context context) { + super(context); + } + + public SquaredImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + } +} diff --git a/lib/src/main/java/com/litao/android/lib/view/SquaredView.java b/lib/src/main/java/com/litao/android/lib/view/SquaredView.java new file mode 100644 index 0000000..b9e67e6 --- /dev/null +++ b/lib/src/main/java/com/litao/android/lib/view/SquaredView.java @@ -0,0 +1,28 @@ +package com.litao.android.lib.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +/** + * Created by 李涛 on 16/5/2. + */ +public class SquaredView extends View { + public SquaredView(Context context) { + super(context); + } + + public SquaredView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + } + + public void toggle(){ + setVisibility(getVisibility()==VISIBLE?INVISIBLE:VISIBLE); + } +} diff --git a/lib/src/main/res/drawable/item_list_click_color.xml b/lib/src/main/res/drawable/item_list_click_color.xml new file mode 100644 index 0000000..f1c7807 --- /dev/null +++ b/lib/src/main/res/drawable/item_list_click_color.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/src/main/res/layout/item_grid_album.xml b/lib/src/main/res/layout/item_grid_album.xml new file mode 100644 index 0000000..64fddab --- /dev/null +++ b/lib/src/main/res/layout/item_grid_album.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/lib/src/main/res/layout/item_list_album.xml b/lib/src/main/res/layout/item_list_album.xml new file mode 100644 index 0000000..97daeb9 --- /dev/null +++ b/lib/src/main/res/layout/item_list_album.xml @@ -0,0 +1,44 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/src/main/res/layout/item_photo.xml b/lib/src/main/res/layout/item_photo.xml new file mode 100644 index 0000000..57bc8d9 --- /dev/null +++ b/lib/src/main/res/layout/item_photo.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/lib/src/main/res/layout/layout_albums.xml b/lib/src/main/res/layout/layout_albums.xml new file mode 100644 index 0000000..b8eb71e --- /dev/null +++ b/lib/src/main/res/layout/layout_albums.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/lib/src/main/res/layout/layout_photos.xml b/lib/src/main/res/layout/layout_photos.xml new file mode 100644 index 0000000..870bfd3 --- /dev/null +++ b/lib/src/main/res/layout/layout_photos.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/lib/src/main/res/mipmap-hdpi/check.png b/lib/src/main/res/mipmap-hdpi/check.png new file mode 100755 index 0000000..f00b4ff Binary files /dev/null and b/lib/src/main/res/mipmap-hdpi/check.png differ diff --git a/lib/src/main/res/mipmap-mdpi/check.png b/lib/src/main/res/mipmap-mdpi/check.png new file mode 100755 index 0000000..6ff1a82 Binary files /dev/null and b/lib/src/main/res/mipmap-mdpi/check.png differ diff --git a/lib/src/main/res/mipmap-xhdpi/camera.png b/lib/src/main/res/mipmap-xhdpi/camera.png new file mode 100644 index 0000000..735f522 Binary files /dev/null and b/lib/src/main/res/mipmap-xhdpi/camera.png differ diff --git a/lib/src/main/res/mipmap-xhdpi/check.png b/lib/src/main/res/mipmap-xhdpi/check.png new file mode 100755 index 0000000..0ccccb7 Binary files /dev/null and b/lib/src/main/res/mipmap-xhdpi/check.png differ diff --git a/lib/src/main/res/mipmap-xhdpi/default_image.png b/lib/src/main/res/mipmap-xhdpi/default_image.png new file mode 100644 index 0000000..1f51881 Binary files /dev/null and b/lib/src/main/res/mipmap-xhdpi/default_image.png differ diff --git a/lib/src/main/res/mipmap-xhdpi/icon.png b/lib/src/main/res/mipmap-xhdpi/icon.png new file mode 100644 index 0000000..75087d6 Binary files /dev/null and b/lib/src/main/res/mipmap-xhdpi/icon.png differ diff --git a/lib/src/main/res/mipmap-xhdpi/to.png b/lib/src/main/res/mipmap-xhdpi/to.png new file mode 100644 index 0000000..8ca21c8 Binary files /dev/null and b/lib/src/main/res/mipmap-xhdpi/to.png differ diff --git a/lib/src/main/res/values-zh/strings.xml b/lib/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..45d904d --- /dev/null +++ b/lib/src/main/res/values-zh/strings.xml @@ -0,0 +1,5 @@ + + + 相册 + 最多选择%1$d张 + \ No newline at end of file diff --git a/lib/src/main/res/values/attrs.xml b/lib/src/main/res/values/attrs.xml new file mode 100644 index 0000000..2b6083b --- /dev/null +++ b/lib/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/src/main/res/values/colors.xml b/lib/src/main/res/values/colors.xml new file mode 100644 index 0000000..b0ff92a --- /dev/null +++ b/lib/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #FF424242 + #66000000 + #FFFFFFFF + #FF212121 + #FF424242 + \ No newline at end of file diff --git a/lib/src/main/res/values/strings.xml b/lib/src/main/res/values/strings.xml new file mode 100644 index 0000000..d69ea5e --- /dev/null +++ b/lib/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + Lib + Albums + select a maximum of %1$d photos + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..3cbe249 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app', ':lib'