diff --git a/lib_media_selector/README.md b/lib_media_selector/README.md index e35cc58..8311168 100644 --- a/lib_media_selector/README.md +++ b/lib_media_selector/README.md @@ -1,6 +1,6 @@ # 多媒体文件选择库 -目前基于boxing修改 +基于 boxing 修改,因为 [Android Q SQLiteException](https://github.com/bilibili/boxing/issues/154) 问题没有被修复,暂时依赖源码。 ## 1 AndroidN 在 FileProvider 的 xm l配置中加入: @@ -13,7 +13,7 @@ [RotatePhotoView](https://github.com/ChenSiLiang/RotatePhotoView) -## 3 其他备选参考 +## 3 other options -  [boxing](https://github.com/Bilibili/boxing) -  [Matisse](https://github.com/zhihu/Matisse) diff --git a/lib_media_selector/build.gradle b/lib_media_selector/build.gradle index f4e10e8..93b30e5 100644 --- a/lib_media_selector/build.gradle +++ b/lib_media_selector/build.gradle @@ -27,6 +27,10 @@ android { main { java.srcDirs += "src/github/java" res.srcDirs += "src/github/res" + java.srcDirs += "src/boxing/java" + res.srcDirs += "src/boxing/res" + java.srcDirs += "src/boxing-impl/java" + res.srcDirs += "src/boxing-impl/res" } } @@ -36,28 +40,8 @@ dependencies { //support implementation androidLibraries.appcompat implementation androidLibraries.material - /*imageLoader*/ compileOnly thirdLibraries.glide - - implementation('com.bilibili:boxing:1.0.4') { - exclude group: 'com.android.support' - exclude group: 'com.chensl.rotatephotoview' - exclude group: 'com.android.support.test.espresso' - exclude group: 'com.android.support.test' - exclude group: 'org.powermock' - exclude group: 'org.robolectric' - } - - implementation('com.bilibili:boxing-impl:1.0.4') { - exclude group: 'com.android.support' - exclude group: 'com.chensl.rotatephotoview' - exclude group: 'com.android.support.test.espresso' - exclude group: 'com.android.support.test' - exclude group: 'org.powermock' - exclude group: 'org.robolectric' - } - implementation('com.github.yalantis:ucrop:2.2.4') { exclude group: 'com.squareup.okhttp3' } diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/BoxingResHelper.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/BoxingResHelper.java new file mode 100644 index 0000000..1c95367 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/BoxingResHelper.java @@ -0,0 +1,32 @@ +package com.bilibili.boxing_impl; + +import com.bilibili.boxing.model.BoxingManager; +import com.ztiany.mediaselector.R; + +import androidx.annotation.DrawableRes; + +/** + * Help getting the resource in config. + * + * @author ChenSL + */ + +public class BoxingResHelper { + + @DrawableRes + public static int getMediaCheckedRes() { + int result = BoxingManager.getInstance().getBoxingConfig().getMediaCheckedRes(); + return result > 0 ? result : R.drawable.ic_boxing_checked; + } + + @DrawableRes + public static int getMediaUncheckedRes() { + int result = BoxingManager.getInstance().getBoxingConfig().getMediaUnCheckedRes(); + return result > 0 ? result : R.drawable.shape_boxing_unchecked; + } + + @DrawableRes + public static int getCameraRes() { + return BoxingManager.getInstance().getBoxingConfig().getCameraRes(); + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/WindowManagerHelper.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/WindowManagerHelper.java new file mode 100644 index 0000000..137903c --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/WindowManagerHelper.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Display; +import android.view.WindowManager; + +/** + * @author ChenSL + */ +public class WindowManagerHelper { + private static WindowManager getWindowManager(Context context) { + Object service = context.getSystemService(Context.WINDOW_SERVICE); + if (service == null) + return null; + + return (WindowManager) service; + } + + private static Display getDefaultDisplay(Context context) { + WindowManager wm = getWindowManager(context); + if (wm == null) + return null; + + return wm.getDefaultDisplay(); + } + + public static int getScreenHeight(Context context) { + DisplayMetrics dm = getDisplayMetrics(context); + if (dm != null) { + return dm.heightPixels; + } + return 0; + } + + public static int getScreenWidth(Context context) { + DisplayMetrics dm = getDisplayMetrics(context); + if (dm != null) { + return dm.widthPixels; + } + return 0; + } + + private static DisplayMetrics getDisplayMetrics(Context context) { + Display display = getDefaultDisplay(context); + if (display != null) { + DisplayMetrics result = new DisplayMetrics(); + display.getMetrics(result); + return result; + } + return null; + } + + public static int getStatusBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + public static int getToolbarHeight(Context context) { + TypedValue tv = new TypedValue(); + if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { + return TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); + } + return 0; + } + +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/adapter/BoxingAlbumAdapter.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/adapter/BoxingAlbumAdapter.java new file mode 100644 index 0000000..7e7aaf3 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/adapter/BoxingAlbumAdapter.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.adapter; + +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bilibili.boxing.BoxingMediaLoader; +import com.bilibili.boxing.model.BoxingManager; +import com.bilibili.boxing.model.entity.AlbumEntity; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.recyclerview.widget.RecyclerView; + + +/** + * Album window adapter. + * + * @author ChenSL + */ +public class BoxingAlbumAdapter extends RecyclerView.Adapter implements View.OnClickListener { + private static final String UNKNOW_ALBUM_NAME = "?"; + + private int mCurrentAlbumPos; + + private List mAlums; + private LayoutInflater mInflater; + private OnAlbumClickListener mAlbumOnClickListener; + private int mDefaultRes; + + public BoxingAlbumAdapter(Context context) { + this.mAlums = new ArrayList<>(); + this.mAlums.add(AlbumEntity.createDefaultAlbum()); + this.mInflater = LayoutInflater.from(context); + this.mDefaultRes = BoxingManager.getInstance().getBoxingConfig().getAlbumPlaceHolderRes(); + } + + public void setAlbumOnClickListener(OnAlbumClickListener albumOnClickListener) { + this.mAlbumOnClickListener = albumOnClickListener; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new AlbumViewHolder(mInflater.inflate(R.layout.layout_boxing_album_item, parent, false)); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final AlbumViewHolder albumViewHolder = (AlbumViewHolder) holder; + albumViewHolder.mCoverImg.setImageResource(mDefaultRes); + final int adapterPos = holder.getAdapterPosition(); + final AlbumEntity album = mAlums.get(adapterPos); + + if (album != null && album.hasImages()) { + String albumName = TextUtils.isEmpty(album.mBucketName) ? + albumViewHolder.mNameTxt.getContext().getString(R.string.boxing_default_album_name) :album.mBucketName; + albumViewHolder.mNameTxt.setText(albumName); + ImageMedia media = (ImageMedia) album.mImageList.get(0); + if (media != null) { + BoxingMediaLoader.getInstance().displayThumbnail(albumViewHolder.mCoverImg, media.getPath(), 50, 50); + albumViewHolder.mCoverImg.setTag(R.string.boxing_app_name, media.getPath()); + } + albumViewHolder.mLayout.setTag(adapterPos); + albumViewHolder.mLayout.setOnClickListener(this); + albumViewHolder.mCheckedImg.setVisibility(album.mIsSelected ? View.VISIBLE : View.GONE); + albumViewHolder.mSizeTxt.setText(albumViewHolder.mSizeTxt. + getResources().getString(R.string.boxing_album_images_fmt, album.mCount)); + } else { + albumViewHolder.mNameTxt.setText(UNKNOW_ALBUM_NAME); + albumViewHolder.mSizeTxt.setVisibility(View.GONE); + } + } + + public void addAllData(List alums) { + mAlums.clear(); + mAlums.addAll(alums); + notifyDataSetChanged(); + } + + public List getAlums() { + return mAlums; + } + + public int getCurrentAlbumPos() { + return mCurrentAlbumPos; + } + + public void setCurrentAlbumPos(int currentAlbumPos) { + mCurrentAlbumPos = currentAlbumPos; + } + + public AlbumEntity getCurrentAlbum() { + if (mAlums == null || mAlums.size() <= 0) { + return null; + } + return mAlums.get(mCurrentAlbumPos); + } + + @Override + public int getItemCount() { + return mAlums != null ? mAlums.size() : 0; + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.album_layout) { + if (mAlbumOnClickListener != null) { + mAlbumOnClickListener.onClick(v, (Integer) v.getTag()); + } + } + } + + private static class AlbumViewHolder extends RecyclerView.ViewHolder { + ImageView mCoverImg; + TextView mNameTxt; + TextView mSizeTxt; + View mLayout; + ImageView mCheckedImg; + + AlbumViewHolder(final View itemView) { + super(itemView); + mCoverImg = (ImageView) itemView.findViewById(R.id.album_thumbnail); + mNameTxt = (TextView) itemView.findViewById(R.id.album_name); + mSizeTxt = (TextView) itemView.findViewById(R.id.album_size); + mLayout = itemView.findViewById(R.id.album_layout); + mCheckedImg = (ImageView) itemView.findViewById(R.id.album_checked); + } + } + + public interface OnAlbumClickListener { + void onClick(View view, int pos); + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/adapter/BoxingMediaAdapter.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/adapter/BoxingMediaAdapter.java new file mode 100644 index 0000000..fe60212 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/adapter/BoxingMediaAdapter.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bilibili.boxing.model.BoxingManager; +import com.bilibili.boxing.model.config.BoxingConfig; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.bilibili.boxing_impl.BoxingResHelper; +import com.bilibili.boxing_impl.view.MediaItemLayout; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + + +/** + * A RecyclerView.Adapter for image or video picker showing. + * + * @author ChenSL + */ +public class BoxingMediaAdapter extends RecyclerView.Adapter { + private static final int CAMERA_TYPE = 0; + private static final int NORMAL_TYPE = 1; + + private int mOffset; + private boolean mMultiImageMode; + + private List mMedias; + private List mSelectedMedias; + private LayoutInflater mInflater; + private BoxingConfig mMediaConfig; + private View.OnClickListener mOnCameraClickListener; + private View.OnClickListener mOnMediaClickListener; + private OnCheckListener mOnCheckListener; + private OnMediaCheckedListener mOnCheckedListener; + private int mDefaultRes; + + public BoxingMediaAdapter(Context context) { + this.mInflater = LayoutInflater.from(context); + this.mMedias = new ArrayList<>(); + this.mSelectedMedias = new ArrayList<>(9); + this.mMediaConfig = BoxingManager.getInstance().getBoxingConfig(); + this.mOffset = mMediaConfig.isNeedCamera() ? 1 : 0; + this.mMultiImageMode = mMediaConfig.getMode() == BoxingConfig.Mode.MULTI_IMG; + this.mOnCheckListener = new OnCheckListener(); + this.mDefaultRes = mMediaConfig.getMediaPlaceHolderRes(); + } + + @Override + public int getItemViewType(int position) { + if (position == 0 && mMediaConfig.isNeedCamera()) { + return CAMERA_TYPE; + } + return NORMAL_TYPE; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (CAMERA_TYPE == viewType) { + return new CameraViewHolder(mInflater.inflate(R.layout.layout_boxing_recycleview_header, parent, false)); + } + return new ImageViewHolder(mInflater.inflate(R.layout.layout_boxing_recycleview_item, parent, false)); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof CameraViewHolder) { + CameraViewHolder viewHolder = (CameraViewHolder) holder; + viewHolder.mCameraLayout.setOnClickListener(mOnCameraClickListener); + viewHolder.mCameraImg.setImageResource(BoxingResHelper.getCameraRes()); + } else { + int pos = position - mOffset; + final BaseMedia media = mMedias.get(pos); + final ImageViewHolder vh = (ImageViewHolder) holder; + + vh.mItemLayout.setImageRes(mDefaultRes); + vh.mItemLayout.setTag(media); + + vh.mItemLayout.setOnClickListener(mOnMediaClickListener); + vh.mItemLayout.setTag(R.id.media_item_check, pos); + vh.mItemLayout.setMedia(media); + vh.mItemChecked.setVisibility(mMultiImageMode ? View.VISIBLE : View.GONE); + if (mMultiImageMode && media instanceof ImageMedia) { + vh.mItemLayout.setChecked(((ImageMedia) media).isSelected()); + vh.mItemChecked.setTag(R.id.media_layout, vh.mItemLayout); + vh.mItemChecked.setTag(media); + vh.mItemChecked.setOnClickListener(mOnCheckListener); + } + } + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return mMedias.size() + mOffset; + } + + public void setOnCameraClickListener(View.OnClickListener onCameraClickListener) { + mOnCameraClickListener = onCameraClickListener; + } + + public void setOnCheckedListener(OnMediaCheckedListener onCheckedListener) { + mOnCheckedListener = onCheckedListener; + } + + public void setOnMediaClickListener(View.OnClickListener onMediaClickListener) { + mOnMediaClickListener = onMediaClickListener; + } + + public List getSelectedMedias() { + return mSelectedMedias; + } + + public void setSelectedMedias(List selectedMedias) { + if (selectedMedias == null) { + return; + } + mSelectedMedias.clear(); + mSelectedMedias.addAll(selectedMedias); + notifyDataSetChanged(); + } + + public void addAllData(@NonNull List data) { + int oldSize = mMedias.size(); + this.mMedias.addAll(data); + int size = data.size(); + notifyItemRangeInserted(oldSize, size); + } + + public void clearData() { + int size = mMedias.size(); + this.mMedias.clear(); + notifyItemRangeRemoved(0, size); + } + + public List getAllMedias() { + return mMedias; + } + + private static class ImageViewHolder extends RecyclerView.ViewHolder { + MediaItemLayout mItemLayout; + View mItemChecked; + + ImageViewHolder(View itemView) { + super(itemView); + mItemLayout = (MediaItemLayout) itemView.findViewById(R.id.media_layout); + mItemChecked = itemView.findViewById(R.id.media_item_check); + } + } + + private static class CameraViewHolder extends RecyclerView.ViewHolder { + View mCameraLayout; + ImageView mCameraImg; + + CameraViewHolder(final View itemView) { + super(itemView); + mCameraLayout = itemView.findViewById(R.id.camera_layout); + mCameraImg = (ImageView) itemView.findViewById(R.id.camera_img); + } + } + + private class OnCheckListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + MediaItemLayout itemLayout = (MediaItemLayout) v.getTag(R.id.media_layout); + BaseMedia media = (BaseMedia) v.getTag(); + if (mMediaConfig.getMode() == BoxingConfig.Mode.MULTI_IMG) { + if (mOnCheckedListener != null) { + mOnCheckedListener.onChecked(itemLayout, media); + } + } + } + } + + public interface OnMediaCheckedListener { + /** + * In multi image mode, selecting a {@link BaseMedia} or undo. + */ + void onChecked(View v, BaseMedia iMedia); + } + +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingActivity.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingActivity.java new file mode 100644 index 0000000..289ae9e --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingActivity.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.ui; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import com.bilibili.boxing.AbsBoxingActivity; +import com.bilibili.boxing.AbsBoxingViewFragment; +import com.bilibili.boxing.model.config.BoxingConfig; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + +/** + * Default UI Activity for simplest usage. + * A simple subclass of {@link AbsBoxingActivity}. Holding a {@link AbsBoxingViewFragment} to display medias. + */ +public class BoxingActivity extends AbsBoxingActivity { + private BoxingViewFragment mPickerFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_boxing); + createToolbar(); + setTitleTxt(getBoxingConfig()); + } + + @NonNull + @Override + public AbsBoxingViewFragment onCreateBoxingView(ArrayList medias) { + mPickerFragment = (BoxingViewFragment) getSupportFragmentManager().findFragmentByTag(BoxingViewFragment.TAG); + if (mPickerFragment == null) { + mPickerFragment = (BoxingViewFragment) BoxingViewFragment.newInstance().setSelectedBundle(medias); + getSupportFragmentManager().beginTransaction().replace(R.id.content_layout, mPickerFragment, BoxingViewFragment.TAG).commit(); + } + return mPickerFragment; + } + + private void createToolbar() { + Toolbar bar = (Toolbar) findViewById(R.id.nav_top_bar); + setSupportActionBar(bar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); + bar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onBackPressed(); + } + }); + } + + private void setTitleTxt(BoxingConfig config) { + TextView titleTxt = (TextView) findViewById(R.id.pick_album_txt); + if (config.getMode() == BoxingConfig.Mode.VIDEO) { + titleTxt.setText(R.string.boxing_video_title); + titleTxt.setCompoundDrawables(null, null, null, null); + return; + } + mPickerFragment.setTitleTxt(titleTxt); + } + + @Override + public void onBoxingFinish(Intent intent, @Nullable List medias) { + setResult(Activity.RESULT_OK, intent); + finish(); + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBaseFragment.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBaseFragment.java new file mode 100644 index 0000000..2ea66ba --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBaseFragment.java @@ -0,0 +1,36 @@ +package com.bilibili.boxing_impl.ui; + +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +/** + * Created by ChenSL on 2017/4/5. + */ + +public class BoxingBaseFragment extends Fragment { + private boolean mNeedPendingUserVisibileHint; + private boolean mLastUserVisibileHint; + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (mNeedPendingUserVisibileHint) { + setUserVisibleCompat(mLastUserVisibileHint); + } + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (getActivity() == null) { + mNeedPendingUserVisibileHint = true; + mLastUserVisibileHint = isVisibleToUser; + } else { + setUserVisibleCompat(isVisibleToUser); + } + } + + void setUserVisibleCompat(boolean userVisibleCompat) { + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBottomSheetActivity.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBottomSheetActivity.java new file mode 100644 index 0000000..d37d51b --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBottomSheetActivity.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.bilibili.boxing.AbsBoxingActivity; +import com.bilibili.boxing.AbsBoxingViewFragment; +import com.bilibili.boxing.BoxingMediaLoader; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + +/** + * Default UI Activity for simplest usage, containing layout achieve {@link BottomSheetBehavior}. + * Only support SINGLE_IMG and VIDEO Mode. + * + * @author ChenSL + */ +public class BoxingBottomSheetActivity extends AbsBoxingActivity implements View.OnClickListener { + private BottomSheetBehavior mBehavior; + private ImageView mImage; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_boxing_bottom_sheet); + createToolbar(); + + FrameLayout bottomSheet = (FrameLayout) findViewById(R.id.content_layout); + mBehavior = BottomSheetBehavior.from(bottomSheet); + mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + + mImage = (ImageView) findViewById(R.id.media_result); + mImage.setOnClickListener(this); + } + + @NonNull + @Override + public AbsBoxingViewFragment onCreateBoxingView(ArrayList medias) { + BoxingBottomSheetFragment fragment = (BoxingBottomSheetFragment) getSupportFragmentManager().findFragmentByTag(BoxingBottomSheetFragment.TAG); + if (fragment == null) { + fragment = BoxingBottomSheetFragment.newInstance(); + getSupportFragmentManager().beginTransaction() + .add(R.id.content_layout, fragment, BoxingBottomSheetFragment.TAG).commit(); + } + return fragment; + } + + private void createToolbar() { + Toolbar bar = (Toolbar) findViewById(R.id.nav_top_bar); + setSupportActionBar(bar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(R.string.boxing_default_album); + bar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onBackPressed(); + } + }); + } + + private boolean hideBottomSheet() { + if (mBehavior != null && mBehavior.getState() != BottomSheetBehavior.STATE_HIDDEN) { + mBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + return true; + } + return false; + } + + private boolean collapseBottomSheet() { + if (mBehavior != null && mBehavior.getState() != BottomSheetBehavior.STATE_COLLAPSED) { + mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + return true; + } + return false; + } + + private void toggleBottomSheet() { + if (mBehavior == null) { + return; + } + if (mBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { + mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } else { + mBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + } + } + + @Override + public void onBackPressed() { + if (hideBottomSheet()) { + return; + } + super.onBackPressed(); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.media_result) { + toggleBottomSheet(); + } + } + + + @Override + public void onBoxingFinish(Intent intent, @Nullable List medias) { + if (mImage != null && medias != null && !medias.isEmpty()) { + ImageMedia imageMedia = (ImageMedia) medias.get(0); + BoxingMediaLoader.getInstance().displayRaw(mImage, imageMedia.getPath(), 1080, 720, null); + } + hideBottomSheet(); + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBottomSheetFragment.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBottomSheetFragment.java new file mode 100644 index 0000000..2837621 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingBottomSheetFragment.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.ui; + +import android.Manifest; +import android.app.ProgressDialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.bilibili.boxing.AbsBoxingViewFragment; +import com.bilibili.boxing.model.BoxingManager; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.bilibili.boxing.presenter.PickerContract; +import com.bilibili.boxing.utils.BoxingFileHelper; +import com.bilibili.boxing_impl.adapter.BoxingMediaAdapter; +import com.bilibili.boxing_impl.view.HackyGridLayoutManager; +import com.bilibili.boxing_impl.view.SpacesItemDecoration; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * the most easy to implement {@link PickerContract.View} to show medias with google's Bottom Sheet
+ * for simplest purpose, it only support SINGLE_IMG and VIDEO Mode. + * for MULTI_IMG mode, use {@link BoxingViewFragment} instead. + * + * @author ChenSL + */ +public class BoxingBottomSheetFragment extends AbsBoxingViewFragment implements View.OnClickListener { + public static final String TAG = "com.bilibili.boxing_impl.ui.BoxingBottomSheetFragment"; + + private static final int GRID_COUNT = 3; + + private boolean mIsCamera; + + private BoxingMediaAdapter mMediaAdapter; + private ProgressDialog mDialog; + private RecyclerView mRecycleView; + private TextView mEmptyTxt; + private ProgressBar mLoadingView; + + public static BoxingBottomSheetFragment newInstance() { + return new BoxingBottomSheetFragment(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mMediaAdapter = new BoxingMediaAdapter(getActivity()); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_boxing_bottom_sheet, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mEmptyTxt = (TextView) view.findViewById(R.id.empty_txt); + mRecycleView = (RecyclerView) view.findViewById(R.id.media_recycleview); + mRecycleView.setHasFixedSize(true); + mLoadingView = (ProgressBar) view.findViewById(R.id.loading); + GridLayoutManager gridLayoutManager = new HackyGridLayoutManager(getActivity(), GRID_COUNT); + gridLayoutManager.setSmoothScrollbarEnabled(true); + mRecycleView.setLayoutManager(gridLayoutManager); + mRecycleView.addItemDecoration(new SpacesItemDecoration(getResources().getDimensionPixelOffset(R.dimen.boxing_media_margin), GRID_COUNT)); + mRecycleView.setAdapter(mMediaAdapter); + mRecycleView.addOnScrollListener(new ScrollListener()); + mMediaAdapter.setOnMediaClickListener(new OnMediaClickListener()); + mMediaAdapter.setOnCameraClickListener(new OnCameraClickListener()); + view.findViewById(R.id.finish_txt).setOnClickListener(this); + } + + + + @Override + public void onCameraActivityResult(int requestCode, int resultCode) { + showProgressDialog(); + super.onCameraActivityResult(requestCode, resultCode); + } + + private void showProgressDialog() { + if (mDialog == null) { + mDialog = new ProgressDialog(getActivity()); + mDialog.setIndeterminate(true); + mDialog.setMessage(getString(R.string.boxing_handling)); + } + if (!mDialog.isShowing()) { + mDialog.show(); + } + } + + private void dismissProgressDialog() { + if (mDialog != null && mDialog.isShowing()) { + mDialog.hide(); + mDialog.dismiss(); + } + } + + + @Override + public void showMedia(List medias, int count) { + if (medias == null || isEmptyData(medias) + && isEmptyData(mMediaAdapter.getAllMedias())) { + showEmptyData(); + return; + } + showData(); + mMediaAdapter.addAllData(medias); + } + + private boolean isEmptyData(List medias) { + return medias.isEmpty() && !BoxingManager.getInstance().getBoxingConfig().isNeedCamera(); + } + + private void showEmptyData() { + mEmptyTxt.setVisibility(View.VISIBLE); + mRecycleView.setVisibility(View.GONE); + mLoadingView.setVisibility(View.GONE); + } + + private void showData() { + mLoadingView.setVisibility(View.GONE); + mEmptyTxt.setVisibility(View.GONE); + mRecycleView.setVisibility(View.VISIBLE); + } + + @Override + public void onCameraFinish(BaseMedia media) { + dismissProgressDialog(); + mIsCamera = false; + if (media != null) { + List selectedMedias = mMediaAdapter.getSelectedMedias(); + selectedMedias.add(media); + BoxingBottomSheetFragment.this.onFinish(selectedMedias); + } + } + + @Override + public void onCameraError() { + mIsCamera = false; + dismissProgressDialog(); + } + + @Override + public void startLoading() { + loadMedias(); + } + + @Override + public void onRequestPermissionError(String[] permissions, Exception e) { + if (permissions.length > 0) { + if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + showEmptyData(); + Toast.makeText(getContext(), R.string.boxing_storage_permission_deny, Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onRequestPermissionSuc(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (permissions[0].equals(STORAGE_PERMISSIONS[0])) { + startLoading(); + } + } + + + @Override + public void clearMedia() { + mMediaAdapter.clearData(); + } + + + @Override + public void onClick(View v) { + int id = v.getId(); + if (R.id.finish_txt == id) { + onFinish(null); + } + } + + + private class OnMediaClickListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + ArrayList iMedias = new ArrayList<>(); + BaseMedia media = (BaseMedia) v.getTag(); + iMedias.add(media); + onFinish(iMedias); + } + } + + private class OnCameraClickListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + if (!mIsCamera) { + mIsCamera = true; + startCamera(getActivity(), BoxingBottomSheetFragment.this, BoxingFileHelper.DEFAULT_SUB_DIR); + } + } + } + + private class ScrollListener extends RecyclerView.OnScrollListener { + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + final int childCount = recyclerView.getChildCount(); + if (childCount > 0) { + View lastChild = recyclerView.getChildAt(childCount - 1); + RecyclerView.Adapter outerAdapter = recyclerView.getAdapter(); + int lastVisible = recyclerView.getChildAdapterPosition(lastChild); + if (lastVisible == outerAdapter.getItemCount() - 1 && hasNextPage() && canLoadNextPage()) { + onLoadNextPage(); + } + } + } + } + +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingRawImageFragment.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingRawImageFragment.java new file mode 100644 index 0000000..7e85b1c --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingRawImageFragment.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.ui; + +import android.app.Activity; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import com.bilibili.boxing.AbsBoxingViewActivity; +import com.bilibili.boxing.loader.IBoxingCallback; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.bilibili.boxing.utils.BoxingLog; +import com.ztiany.mediaselector.R; + +import java.lang.ref.WeakReference; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import uk.co.senab.photoview.PhotoView; +import uk.co.senab.photoview.PhotoViewAttacher; + +/** + * show raw image with the control of finger gesture. + * + * @author ChenSL + */ +public class BoxingRawImageFragment extends BoxingBaseFragment { + private static final String BUNDLE_IMAGE = "com.bilibili.boxing_impl.ui.BoxingRawImageFragment.image"; + private static final int MAX_SCALE = 15; + private static final long MAX_IMAGE1 = 1024 * 1024L; + private static final long MAX_IMAGE2 = 4 * MAX_IMAGE1; + + private PhotoView mImageView; + private ProgressBar mProgress; + private ImageMedia mMedia; + private PhotoViewAttacher mAttacher; + + public static BoxingRawImageFragment newInstance(@NonNull ImageMedia image) { + BoxingRawImageFragment fragment = new BoxingRawImageFragment(); + Bundle args = new Bundle(); + args.putParcelable(BUNDLE_IMAGE, image); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mMedia = getArguments().getParcelable(BUNDLE_IMAGE); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_boxing_raw_image, container, false); + } + + @Override + public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mProgress = (ProgressBar) view.findViewById(R.id.loading); + mImageView = (PhotoView) view.findViewById(R.id.photo_view); + mAttacher = new PhotoViewAttacher(mImageView); + mAttacher.setRotatable(true); + mAttacher.setToRightAngle(true); + } + + @Override + void setUserVisibleCompat(boolean isVisibleToUser) { + if (isVisibleToUser) { + Point point = getResizePointer(mMedia.getSize()); + ((AbsBoxingViewActivity) getActivity()).loadRawImage(mImageView, mMedia.getPath(), point.x, point.y, new BoxingCallback(this)); + } + } + + /** + * resize the image or not according to size. + * + * @param size the size of image + */ + private Point getResizePointer(long size) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + Point point = new Point(metrics.widthPixels, metrics.heightPixels); + if (size >= MAX_IMAGE2) { + point.x >>= 2; + point.y >>= 2; + } else if (size >= MAX_IMAGE1) { + point.x >>= 1; + point.y >>= 1; + } else if (size > 0) { + // avoid some images do not have a size. + point.x = 0; + point.y = 0; + } + return point; + } + + private void dismissProgressDialog() { + if (mProgress != null) { + mProgress.setVisibility(View.GONE); + } + BoxingViewActivity activity = getThisActivity(); + if (activity != null && activity.mProgressBar != null) { + activity.mProgressBar.setVisibility(View.GONE); + } + } + + private BoxingViewActivity getThisActivity() { + Activity activity = getActivity(); + if (activity instanceof BoxingViewActivity) { + return (BoxingViewActivity) activity; + } + return null; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mAttacher != null) { + mAttacher.cleanup(); + mAttacher = null; + mImageView = null; + } + } + + private static class BoxingCallback implements IBoxingCallback { + private WeakReference mWr; + + BoxingCallback(BoxingRawImageFragment fragment) { + mWr = new WeakReference<>(fragment); + } + + @Override + public void onSuccess() { + if (mWr.get() == null || mWr.get().mImageView == null) { + return; + } + mWr.get().dismissProgressDialog(); + Drawable drawable = mWr.get().mImageView.getDrawable(); + PhotoViewAttacher attacher = mWr.get().mAttacher; + if (attacher != null) { + if (drawable.getIntrinsicHeight() > (drawable.getIntrinsicWidth() << 2)) { + // handle the super height image. + int scale = drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth(); + scale = Math.min(MAX_SCALE, scale); + attacher.setMaximumScale(scale); + attacher.setScale(scale, true); + } + attacher.update(); + } + BoxingViewActivity activity = mWr.get().getThisActivity(); + if (activity != null && activity.mGallery != null) { + activity.mGallery.setVisibility(View.VISIBLE); + } + } + + @Override + public void onFail(Throwable t) { + if (mWr.get() == null) { + return; + } + BoxingLog.d(t != null ? t.getMessage() : "load raw image error."); + mWr.get().dismissProgressDialog(); + mWr.get().mImageView.setImageResource(R.drawable.ic_boxing_broken_image); + if (mWr.get().mAttacher != null) { + mWr.get().mAttacher.update(); + } + } + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingViewActivity.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingViewActivity.java new file mode 100644 index 0000000..3174279 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingViewActivity.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.Toast; + +import com.bilibili.boxing.AbsBoxingViewActivity; +import com.bilibili.boxing.Boxing; +import com.bilibili.boxing.model.BoxingManager; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.bilibili.boxing.model.task.IMediaTask; +import com.bilibili.boxing_impl.BoxingResHelper; +import com.bilibili.boxing_impl.view.HackyViewPager; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; + +/** + * An Activity to show raw image by holding {@link BoxingViewFragment}. + * + * @author ChenSL + */ +public class BoxingViewActivity extends AbsBoxingViewActivity { + public static final String EXTRA_TYPE_BACK = "com.bilibili.boxing_impl.ui.BoxingViewActivity.type_back"; + + HackyViewPager mGallery; + ProgressBar mProgressBar; + + private boolean mNeedEdit; + private boolean mNeedLoading; + private boolean mFinishLoading; + private boolean mNeedAllCount = true; + private int mCurrentPage; + private int mTotalCount; + private int mStartPos; + private int mPos; + private int mMaxCount; + + private String mAlbumId; + private Toolbar mToolbar; + private ImagesAdapter mAdapter; + private ImageMedia mCurrentImageItem; + private Button mOkBtn; + private ArrayList mImages; + private ArrayList mSelectedImages; + private MenuItem mSelectedMenuItem; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_boxing_view); + createToolbar(); + initData(); + initView(); + startLoading(); + } + + private void createToolbar() { + mToolbar = (Toolbar) findViewById(R.id.nav_top_bar); + setSupportActionBar(mToolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onBackPressed(); + } + }); + getSupportActionBar().setDisplayShowTitleEnabled(false); + } + + private void initData() { + mSelectedImages = getSelectedImages(); + mAlbumId = getAlbumId(); + mStartPos = getStartPos(); + mNeedLoading = BoxingManager.getInstance().getBoxingConfig().isNeedLoading(); + mNeedEdit = BoxingManager.getInstance().getBoxingConfig().isNeedEdit(); + mMaxCount = getMaxCount(); + mImages = new ArrayList<>(); + if (!mNeedLoading && mSelectedImages != null) { + mImages.addAll(mSelectedImages); + } + } + + private void initView() { + mAdapter = new ImagesAdapter(getSupportFragmentManager()); + mOkBtn = (Button) findViewById(R.id.image_items_ok); + mGallery = (HackyViewPager) findViewById(R.id.pager); + mProgressBar = (ProgressBar) findViewById(R.id.loading); + mGallery.setAdapter(mAdapter); + mGallery.addOnPageChangeListener(new OnPagerChangeListener()); + if (!mNeedEdit) { + View chooseLayout = findViewById(R.id.item_choose_layout); + chooseLayout.setVisibility(View.GONE); + } else { + setOkTextNumber(); + mOkBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finishByBackPressed(false); + } + }); + } + } + + private void setOkTextNumber() { + if (mNeedEdit) { + int selectedSize = mSelectedImages.size(); + int size = Math.max(mSelectedImages.size(), mMaxCount); + mOkBtn.setText(getString(R.string.boxing_image_preview_ok_fmt, String.valueOf(selectedSize) + , String.valueOf(size))); + mOkBtn.setEnabled(selectedSize > 0); + } + } + + private void finishByBackPressed(boolean value) { + Intent intent = new Intent(); + intent.putParcelableArrayListExtra(Boxing.EXTRA_SELECTED_MEDIA, mSelectedImages); + intent.putExtra(EXTRA_TYPE_BACK, value); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + if (mNeedEdit) { + getMenuInflater().inflate(R.menu.activity_boxing_image_viewer, menu); + mSelectedMenuItem = menu.findItem(R.id.menu_image_item_selected); + if (mCurrentImageItem != null) { + setMenuIcon(mCurrentImageItem.isSelected()); + } else { + setMenuIcon(false); + } + return true; + } + return false; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.menu_image_item_selected) { + if (mCurrentImageItem == null) { + return false; + } + if (mSelectedImages.size() >= mMaxCount && !mCurrentImageItem.isSelected()) { + String warning = getString(R.string.boxing_max_image_over_fmt, mMaxCount); + Toast.makeText(this, warning, Toast.LENGTH_SHORT).show(); + return true; + } + if (mCurrentImageItem.isSelected()) { + cancelImage(); + } else { + if (!mSelectedImages.contains(mCurrentImageItem)) { + if (mCurrentImageItem.isGifOverSize()) { + Toast.makeText(getApplicationContext(), R.string.boxing_gif_too_big, Toast.LENGTH_SHORT).show(); + return true; + } + mCurrentImageItem.setSelected(true); + mSelectedImages.add(mCurrentImageItem); + } + } + setOkTextNumber(); + setMenuIcon(mCurrentImageItem.isSelected()); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void cancelImage() { + if (mSelectedImages.contains(mCurrentImageItem)) { + mSelectedImages.remove(mCurrentImageItem); + } + mCurrentImageItem.setSelected(false); + } + + + private void setMenuIcon(boolean isSelected) { + if (mNeedEdit) { + mSelectedMenuItem.setIcon(isSelected ? BoxingResHelper.getMediaCheckedRes(): BoxingResHelper.getMediaUncheckedRes()); + } + } + + @Override + public void startLoading() { + if (!mNeedLoading) { + mCurrentImageItem = (ImageMedia) mSelectedImages.get(mStartPos); + mToolbar.setTitle(getString(R.string.boxing_image_preview_title_fmt, String.valueOf(mStartPos + 1) + , String.valueOf(mSelectedImages.size()))); + mProgressBar.setVisibility(View.GONE); + mGallery.setVisibility(View.VISIBLE); + mAdapter.setMedias(mImages); + if (mStartPos > 0 && mStartPos < mSelectedImages.size()) { + mGallery.setCurrentItem(mStartPos, false); + } + } else { + loadMedia(mAlbumId, mStartPos, mCurrentPage); + mAdapter.setMedias(mImages); + } + } + + private void loadMedia(String albumId, int startPos, int page) { + this.mPos = startPos; + loadMedias(page, albumId); + } + + @Override + public void showMedia(@Nullable List medias, int totalCount) { + if (medias == null || totalCount <= 0) { + return; + } + mImages.addAll(medias); + mAdapter.notifyDataSetChanged(); + checkSelectedMedia(mImages, mSelectedImages); + setupGallery(); + + if (mToolbar != null && mNeedAllCount) { + mToolbar.setTitle(getString(R.string.boxing_image_preview_title_fmt, + String.valueOf(++mPos), String.valueOf(totalCount))); + mNeedAllCount = false; + } + loadOtherPagesInAlbum(totalCount); + } + + private void setupGallery() { + int startPos = mStartPos; + if (mGallery == null || startPos < 0) { + return; + } + if (startPos < mImages.size() && !mFinishLoading) { + mGallery.setCurrentItem(mStartPos, false); + mCurrentImageItem = (ImageMedia) mImages.get(startPos); + mProgressBar.setVisibility(View.GONE); + mGallery.setVisibility(View.VISIBLE); + mFinishLoading = true; + invalidateOptionsMenu(); + } else if (startPos >= mImages.size()) { + mProgressBar.setVisibility(View.VISIBLE); + mGallery.setVisibility(View.GONE); + } + } + + private void loadOtherPagesInAlbum(int totalCount) { + mTotalCount = totalCount; + if (mCurrentPage <= (mTotalCount / IMediaTask.PAGE_LIMIT)) { + mCurrentPage++; + loadMedia(mAlbumId, mStartPos, mCurrentPage); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + if (mSelectedImages != null) { + outState.putParcelableArrayList(Boxing.EXTRA_SELECTED_MEDIA, mSelectedImages); + } + outState.putString(Boxing.EXTRA_ALBUM_ID, mAlbumId); + super.onSaveInstanceState(outState); + } + + + @Override + public void onBackPressed() { + finishByBackPressed(true); + } + + private class ImagesAdapter extends FragmentStatePagerAdapter { + private ArrayList mMedias; + + ImagesAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int i) { + return BoxingRawImageFragment.newInstance((ImageMedia) mMedias.get(i)); + } + + @Override + public int getCount() { + return mMedias == null ? 0 : mMedias.size(); + } + + public void setMedias(ArrayList medias) { + this.mMedias = medias; + notifyDataSetChanged(); + } + } + + private class OnPagerChangeListener extends ViewPager.SimpleOnPageChangeListener { + + @Override + public void onPageSelected(int position) { + if (mToolbar != null && position < mImages.size()) { + mToolbar.setTitle(getString(R.string.boxing_image_preview_title_fmt, String.valueOf(position + 1) + , mNeedLoading ? String.valueOf(mTotalCount) : String.valueOf(mImages.size()))); + mCurrentImageItem = (ImageMedia) mImages.get(position); + invalidateOptionsMenu(); + } + } + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingViewFragment.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingViewFragment.java new file mode 100644 index 0000000..ee297c2 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/ui/BoxingViewFragment.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.ui; + +import android.Manifest; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.PopupWindow; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.bilibili.boxing.AbsBoxingViewFragment; +import com.bilibili.boxing.Boxing; +import com.bilibili.boxing.model.BoxingManager; +import com.bilibili.boxing.model.config.BoxingConfig; +import com.bilibili.boxing.model.entity.AlbumEntity; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.bilibili.boxing.utils.BoxingFileHelper; +import com.bilibili.boxing_impl.WindowManagerHelper; +import com.bilibili.boxing_impl.adapter.BoxingAlbumAdapter; +import com.bilibili.boxing_impl.adapter.BoxingMediaAdapter; +import com.bilibili.boxing_impl.view.HackyGridLayoutManager; +import com.bilibili.boxing_impl.view.MediaItemLayout; +import com.bilibili.boxing_impl.view.SpacesItemDecoration; +import com.ztiany.mediaselector.R; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * A full implement for {@link com.bilibili.boxing.presenter.PickerContract.View} supporting all the mode + * in {@link BoxingConfig.Mode}. + * use this to pick the picture. + * + * @author ChenSL + */ +public class BoxingViewFragment extends AbsBoxingViewFragment implements View.OnClickListener { + public static final String TAG = "com.bilibili.boxing_impl.ui.BoxingViewFragment"; + private static final int IMAGE_PREVIEW_REQUEST_CODE = 9086; + private static final int IMAGE_CROP_REQUEST_CODE = 9087; + + private static final int GRID_COUNT = 3; + + private boolean mIsPreview; + private boolean mIsCamera; + + private Button mPreBtn; + private Button mOkBtn; + private RecyclerView mRecycleView; + private BoxingMediaAdapter mMediaAdapter; + private BoxingAlbumAdapter mAlbumWindowAdapter; + private ProgressDialog mDialog; + private TextView mEmptyTxt; + private TextView mTitleTxt; + private PopupWindow mAlbumPopWindow; + private ProgressBar mLoadingView; + + private int mMaxCount; + + public static BoxingViewFragment newInstance() { + return new BoxingViewFragment(); + } + + @Override + public void onCreateWithSelectedMedias(Bundle savedInstanceState, @Nullable List selectedMedias) { + mAlbumWindowAdapter = new BoxingAlbumAdapter(getContext()); + mMediaAdapter = new BoxingMediaAdapter(getContext()); + mMediaAdapter.setSelectedMedias(selectedMedias); + mMaxCount = getMaxCount(); + } + + @Override + public void startLoading() { + loadMedias(); + loadAlbum(); + } + + @Override + public void onRequestPermissionError(String[] permissions, Exception e) { + if (permissions.length > 0) { + if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + Toast.makeText(getContext(), R.string.boxing_storage_permission_deny, Toast.LENGTH_SHORT).show(); + showEmptyData(); + } else if (permissions[0].equals(Manifest.permission.CAMERA)){ + Toast.makeText(getContext(), R.string.boxing_camera_permission_deny, Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onRequestPermissionSuc(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (permissions[0].equals(STORAGE_PERMISSIONS[0])) { + startLoading(); + } else if (permissions[0].equals(CAMERA_PERMISSIONS[0])) { + startCamera(getActivity(), this, null); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragmant_boxing_view, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + initViews(view); + super.onViewCreated(view, savedInstanceState); + } + + private void initViews(View view) { + mEmptyTxt = (TextView) view.findViewById(R.id.empty_txt); + mRecycleView = (RecyclerView) view.findViewById(R.id.media_recycleview); + mRecycleView.setHasFixedSize(true); + mLoadingView = (ProgressBar) view.findViewById(R.id.loading); + initRecycleView(); + + boolean isMultiImageMode = BoxingManager.getInstance().getBoxingConfig().isMultiImageMode(); + View multiImageLayout = view.findViewById(R.id.multi_picker_layout); + multiImageLayout.setVisibility(isMultiImageMode ? View.VISIBLE : View.GONE); + if (isMultiImageMode) { + mPreBtn = (Button) view.findViewById(R.id.choose_preview_btn); + mOkBtn = (Button) view.findViewById(R.id.choose_ok_btn); + + mPreBtn.setOnClickListener(this); + mOkBtn.setOnClickListener(this); + updateMultiPickerLayoutState(mMediaAdapter.getSelectedMedias()); + } + } + + private void initRecycleView() { + GridLayoutManager gridLayoutManager = new HackyGridLayoutManager(getActivity(), GRID_COUNT); + gridLayoutManager.setSmoothScrollbarEnabled(true); + mRecycleView.setLayoutManager(gridLayoutManager); + mRecycleView.addItemDecoration(new SpacesItemDecoration(getResources().getDimensionPixelOffset(R.dimen.boxing_media_margin), GRID_COUNT)); + mMediaAdapter.setOnCameraClickListener(new OnCameraClickListener()); + mMediaAdapter.setOnCheckedListener(new OnMediaCheckedListener()); + mMediaAdapter.setOnMediaClickListener(new OnMediaClickListener()); + mRecycleView.setAdapter(mMediaAdapter); + mRecycleView.addOnScrollListener(new ScrollListener()); + } + + @Override + public void showMedia(@Nullable List medias, int allCount) { + if (medias == null || isEmptyData(medias) + && isEmptyData(mMediaAdapter.getAllMedias())) { + showEmptyData(); + return; + } + showData(); + mMediaAdapter.addAllData(medias); + checkSelectedMedia(medias, mMediaAdapter.getSelectedMedias()); + } + + private boolean isEmptyData(List medias) { + return medias.isEmpty() && !BoxingManager.getInstance().getBoxingConfig().isNeedCamera(); + } + + private void showEmptyData() { + mLoadingView.setVisibility(View.GONE); + mEmptyTxt.setVisibility(View.VISIBLE); + mRecycleView.setVisibility(View.GONE); + } + + private void showData() { + mLoadingView.setVisibility(View.GONE); + mEmptyTxt.setVisibility(View.GONE); + mRecycleView.setVisibility(View.VISIBLE); + } + + @Override + public void showAlbum(@Nullable List albums) { + if ((albums == null || albums.isEmpty()) + && mTitleTxt != null) { + mTitleTxt.setCompoundDrawables(null, null, null, null); + mTitleTxt.setOnClickListener(null); + return; + } + mAlbumWindowAdapter.addAllData(albums); + } + + public BoxingMediaAdapter getMediaAdapter() { + return mMediaAdapter; + } + + @Override + public void clearMedia() { + mMediaAdapter.clearData(); + } + + private void updateMultiPickerLayoutState(List medias) { + updateOkBtnState(medias); + updatePreviewBtnState(medias); + } + + private void updatePreviewBtnState(List medias) { + if (mPreBtn == null || medias == null) { + return; + } + boolean enabled = medias.size() > 0 && medias.size() <= mMaxCount; + mPreBtn.setEnabled(enabled); + } + + private void updateOkBtnState(List medias) { + if (mOkBtn == null || medias == null) { + return; + } + boolean enabled = medias.size() > 0 && medias.size() <= mMaxCount; + mOkBtn.setEnabled(enabled); + mOkBtn.setText(enabled ? getString(R.string.boxing_image_select_ok_fmt, String.valueOf(medias.size()) + , String.valueOf(mMaxCount)) : getString(R.string.boxing_ok)); + } + + @Override + public void onCameraFinish(BaseMedia media) { + dismissProgressDialog(); + mIsCamera = false; + if (media == null) { + return; + } + if (hasCropBehavior()) { + startCrop(media, IMAGE_CROP_REQUEST_CODE); + } else if (mMediaAdapter != null && mMediaAdapter.getSelectedMedias() != null) { + List selectedMedias = mMediaAdapter.getSelectedMedias(); + selectedMedias.add(media); + onFinish(selectedMedias); + } + } + + @Override + public void onCameraError() { + mIsCamera = false; + dismissProgressDialog(); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.choose_ok_btn) { + onFinish(mMediaAdapter.getSelectedMedias()); + } else if (id == R.id.choose_preview_btn) { + if (!mIsPreview) { + mIsPreview = true; + ArrayList medias = (ArrayList) mMediaAdapter.getSelectedMedias(); + Boxing.get().withIntent(getActivity(), BoxingViewActivity.class, medias) + .start(this, BoxingViewFragment.IMAGE_PREVIEW_REQUEST_CODE, BoxingConfig.ViewMode.PRE_EDIT); + + } + } + + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (data == null) { + return; + } + if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_PREVIEW_REQUEST_CODE) { + mIsPreview = false; + boolean isBackClick = data.getBooleanExtra(BoxingViewActivity.EXTRA_TYPE_BACK, false); + List selectedMedias = data.getParcelableArrayListExtra(Boxing.EXTRA_SELECTED_MEDIA); + onViewActivityRequest(selectedMedias, mMediaAdapter.getAllMedias(), isBackClick); + if (isBackClick) { + mMediaAdapter.setSelectedMedias(selectedMedias); + } + updateMultiPickerLayoutState(selectedMedias); + } + + } + + private void onViewActivityRequest(List selectedMedias, List allMedias, boolean isBackClick) { + if (isBackClick) { + checkSelectedMedia(allMedias, selectedMedias); + } else { + onFinish(selectedMedias); + } + } + + + @Override + public void onCameraActivityResult(int requestCode, int resultCode) { + showProgressDialog(); + super.onCameraActivityResult(requestCode, resultCode); + } + + private void showProgressDialog() { + if (mDialog == null) { + mDialog = new ProgressDialog(getActivity()); + mDialog.setIndeterminate(true); + mDialog.setMessage(getString(R.string.boxing_handling)); + } + if (!mDialog.isShowing()) { + mDialog.show(); + } + } + + private void dismissProgressDialog() { + if (mDialog != null && mDialog.isShowing()) { + mDialog.hide(); + mDialog.dismiss(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + ArrayList medias = (ArrayList) getMediaAdapter().getSelectedMedias(); + onSaveMedias(outState, medias); + } + + public void setTitleTxt(TextView titleTxt) { + mTitleTxt = titleTxt; + mTitleTxt.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + if (mAlbumPopWindow == null) { + int height = WindowManagerHelper.getScreenHeight(v.getContext()) - + (WindowManagerHelper.getToolbarHeight(v.getContext()) + + WindowManagerHelper.getStatusBarHeight(v.getContext())); + View windowView = createWindowView(); + mAlbumPopWindow = new PopupWindow(windowView, ViewGroup.LayoutParams.MATCH_PARENT, + height, true); + mAlbumPopWindow.setAnimationStyle(R.style.Boxing_PopupAnimation); + mAlbumPopWindow.setOutsideTouchable(true); + mAlbumPopWindow.setBackgroundDrawable(new ColorDrawable + (ContextCompat.getColor(v.getContext(), R.color.boxing_colorPrimaryAlpha))); + mAlbumPopWindow.setContentView(windowView); + } + mAlbumPopWindow.showAsDropDown(v, 0, 0); + } + + @NonNull + private View createWindowView() { + View view = LayoutInflater.from(getActivity()).inflate(R.layout.layout_boxing_album, null); + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.album_recycleview); + recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext(), LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new SpacesItemDecoration(2, 1)); + + View albumShadowLayout = view.findViewById(R.id.album_shadow); + albumShadowLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismissAlbumWindow(); + } + }); + mAlbumWindowAdapter.setAlbumOnClickListener(new OnAlbumItemOnClickListener()); + recyclerView.setAdapter(mAlbumWindowAdapter); + return view; + } + }); + } + + private void dismissAlbumWindow() { + if (mAlbumPopWindow != null && mAlbumPopWindow.isShowing()) { + mAlbumPopWindow.dismiss(); + } + } + + private class ScrollListener extends RecyclerView.OnScrollListener { + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + final int childCount = recyclerView.getChildCount(); + if (childCount > 0) { + View lastChild = recyclerView.getChildAt(childCount - 1); + RecyclerView.Adapter outerAdapter = recyclerView.getAdapter(); + int lastVisible = recyclerView.getChildAdapterPosition(lastChild); + if (lastVisible == outerAdapter.getItemCount() - 1 && hasNextPage() && canLoadNextPage()) { + onLoadNextPage(); + } + } + } + } + + private class OnMediaClickListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + BaseMedia media = (BaseMedia) v.getTag(); + int pos = (int) v.getTag(R.id.media_item_check); + BoxingConfig.Mode mode = BoxingManager.getInstance().getBoxingConfig().getMode(); + if (mode == BoxingConfig.Mode.SINGLE_IMG) { + singleImageClick(media); + } else if (mode == BoxingConfig.Mode.MULTI_IMG) { + multiImageClick(pos); + } else if (mode == BoxingConfig.Mode.VIDEO) { + videoClick(media); + } + } + + private void videoClick(BaseMedia media) { + ArrayList iMedias = new ArrayList<>(); + iMedias.add(media); + onFinish(iMedias); + } + + private void multiImageClick(int pos) { + if (!mIsPreview) { + AlbumEntity albumMedia = mAlbumWindowAdapter.getCurrentAlbum(); + String albumId = albumMedia != null ? albumMedia.mBucketId : AlbumEntity.DEFAULT_NAME; + mIsPreview = true; + + ArrayList medias = (ArrayList) mMediaAdapter.getSelectedMedias(); + + Boxing.get().withIntent(getContext(), BoxingViewActivity.class, medias, pos, albumId) + .start(BoxingViewFragment.this, BoxingViewFragment.IMAGE_PREVIEW_REQUEST_CODE, BoxingConfig.ViewMode.EDIT); + + } + } + + private void singleImageClick(BaseMedia media) { + ArrayList iMedias = new ArrayList<>(); + iMedias.add(media); + if (hasCropBehavior()) { + startCrop(media, IMAGE_CROP_REQUEST_CODE); + } else { + onFinish(iMedias); + } + } + } + + + private class OnCameraClickListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + if (!mIsCamera) { + mIsCamera = true; + startCamera(getActivity(), BoxingViewFragment.this, BoxingFileHelper.DEFAULT_SUB_DIR); + } + } + } + + private class OnMediaCheckedListener implements BoxingMediaAdapter.OnMediaCheckedListener { + + @Override + public void onChecked(View view, BaseMedia iMedia) { + if (!(iMedia instanceof ImageMedia)) { + return; + } + ImageMedia photoMedia = (ImageMedia) iMedia; + boolean isSelected = !photoMedia.isSelected(); + MediaItemLayout layout = (MediaItemLayout) view; + List selectedMedias = mMediaAdapter.getSelectedMedias(); + if (isSelected) { + if (selectedMedias.size() >= mMaxCount) { + String warning = getString(R.string.boxing_too_many_picture_fmt, mMaxCount); + Toast.makeText(getActivity(), warning, Toast.LENGTH_SHORT).show(); + return; + } + if (!selectedMedias.contains(photoMedia)) { + if (photoMedia.isGifOverSize()) { + Toast.makeText(getActivity(), R.string.boxing_gif_too_big, Toast.LENGTH_SHORT).show(); + return; + } + selectedMedias.add(photoMedia); + } + } else { + if (selectedMedias.size() >= 1 && selectedMedias.contains(photoMedia)) { + selectedMedias.remove(photoMedia); + } + } + photoMedia.setSelected(isSelected); + layout.setChecked(isSelected); + updateMultiPickerLayoutState(selectedMedias); + } + } + + private class OnAlbumItemOnClickListener implements BoxingAlbumAdapter.OnAlbumClickListener { + + @Override + public void onClick(View view, int pos) { + BoxingAlbumAdapter adapter = mAlbumWindowAdapter; + if (adapter != null && adapter.getCurrentAlbumPos() != pos) { + List albums = adapter.getAlums(); + adapter.setCurrentAlbumPos(pos); + + AlbumEntity albumMedia = albums.get(pos); + loadMedias(0, albumMedia.mBucketId); + mTitleTxt.setText(albumMedia.mBucketName == null ? getString(R.string.boxing_default_album_name) : albumMedia.mBucketName); + + for (AlbumEntity album : albums) { + album.mIsSelected = false; + } + albumMedia.mIsSelected = true; + adapter.notifyDataSetChanged(); + } + dismissAlbumWindow(); + } + } + +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/HackyGridLayoutManager.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/HackyGridLayoutManager.java new file mode 100644 index 0000000..0498533 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/HackyGridLayoutManager.java @@ -0,0 +1,34 @@ +package com.bilibili.boxing_impl.view; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Created by ChenSL on 2018/3/22. + */ + +public class HackyGridLayoutManager extends GridLayoutManager { + public HackyGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public HackyGridLayoutManager(Context context, int spanCount) { + super(context, spanCount); + } + + public HackyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { + super(context, spanCount, orientation, reverseLayout); + } + + @Override + public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + try { + super.onLayoutChildren(recycler, state); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + } +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/HackyViewPager.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/HackyViewPager.java new file mode 100644 index 0000000..c37adc1 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/HackyViewPager.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.viewpager.widget.ViewPager; + +/** + * https://github.com/chrisbanes/PhotoView/issues/35 + */ +public class HackyViewPager extends ViewPager { + private boolean mIsLocked; + + public HackyViewPager(Context context) { + super(context); + mIsLocked = false; + } + + public HackyViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + mIsLocked = false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!mIsLocked) { + try { + return super.onInterceptTouchEvent(ev); + } catch (IllegalArgumentException e) { + return false; + } + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return !mIsLocked && super.onTouchEvent(event); + } + + public boolean isLocked() { + return mIsLocked; + } + + public void setLocked(boolean isLocked) { + this.mIsLocked = isLocked; + } + +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/MediaItemLayout.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/MediaItemLayout.java new file mode 100644 index 0000000..b262e6a --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/MediaItemLayout.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.view; + +import android.content.Context; +import android.content.res.Configuration; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bilibili.boxing.BoxingMediaLoader; +import com.bilibili.boxing.model.BoxingManager; +import com.bilibili.boxing.model.entity.BaseMedia; +import com.bilibili.boxing.model.entity.impl.ImageMedia; +import com.bilibili.boxing.model.entity.impl.VideoMedia; +import com.bilibili.boxing_impl.BoxingResHelper; +import com.bilibili.boxing_impl.WindowManagerHelper; +import com.ztiany.mediaselector.R; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; + + +/** + * A media layout for {@link androidx.recyclerview.widget.RecyclerView} item, including image and video
+ * + * @author ChenSL + */ +public class MediaItemLayout extends FrameLayout { + private static final int BIG_IMG_SIZE = 5 * 1024 * 1024; + + private ImageView mCheckImg; + private View mVideoLayout; + private View mFontLayout; + private ImageView mCoverImg; + private ScreenType mScreenType; + + private enum ScreenType { + SMALL(100), NORMAL(180), LARGE(320); + int value; + + ScreenType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public MediaItemLayout(Context context) { + this(context, null, 0); + } + + public MediaItemLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public MediaItemLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + View view = LayoutInflater.from(context).inflate(R.layout.layout_boxing_media_item, this, true); + mCoverImg = (ImageView) view.findViewById(R.id.media_item); + mCheckImg = (ImageView) view.findViewById(R.id.media_item_check); + mVideoLayout = view.findViewById(R.id.video_layout); + mFontLayout = view.findViewById(R.id.media_font_layout); + mScreenType = getScreenType(context); + setImageRect(context); + } + + private void setImageRect(Context context) { + int screenHeight = WindowManagerHelper.getScreenHeight(context); + int screenWidth = WindowManagerHelper.getScreenWidth(context); + int width = 100; + if (screenHeight != 0 && screenWidth != 0) { + width = (screenWidth - getResources().getDimensionPixelOffset(R.dimen.boxing_media_margin) * 4) / 3; + } + mCoverImg.getLayoutParams().width = width; + mCoverImg.getLayoutParams().height = width; + mFontLayout.getLayoutParams().width = width; + mFontLayout.getLayoutParams().height = width; + } + + private ScreenType getScreenType(Context context) { + int type = context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; + ScreenType result; + switch (type) { + case Configuration.SCREENLAYOUT_SIZE_SMALL: + result = ScreenType.SMALL; + break; + case Configuration.SCREENLAYOUT_SIZE_NORMAL: + result = ScreenType.NORMAL; + break; + case Configuration.SCREENLAYOUT_SIZE_LARGE: + result = ScreenType.LARGE; + break; + default: + result = ScreenType.NORMAL; + break; + } + return result; + } + + public void setImageRes(@DrawableRes int imageRes) { + if (mCoverImg != null) { + mCoverImg.setImageResource(imageRes); + } + } + + public void setMedia(BaseMedia media) { + if (media instanceof ImageMedia) { + mVideoLayout.setVisibility(GONE); + setCover(((ImageMedia) media).getThumbnailPath()); + } else if (media instanceof VideoMedia) { + mVideoLayout.setVisibility(VISIBLE); + VideoMedia videoMedia = (VideoMedia) media; + TextView durationTxt = ((TextView) mVideoLayout.findViewById(R.id.video_duration_txt)); + durationTxt.setText(videoMedia.getDuration()); + durationTxt.setCompoundDrawablesWithIntrinsicBounds(BoxingManager.getInstance().getBoxingConfig().getVideoDurationRes(), 0, 0, 0); + ((TextView) mVideoLayout.findViewById(R.id.video_size_txt)).setText(videoMedia.getSizeByUnit()); + setCover(videoMedia.getPath()); + } + } + + private void setCover(@NonNull String path) { + if (mCoverImg == null || TextUtils.isEmpty(path)) { + return; + } + mCoverImg.setTag(R.string.boxing_app_name, path); + BoxingMediaLoader.getInstance().displayThumbnail(mCoverImg, path, mScreenType.getValue(), mScreenType.getValue()); + } + + @SuppressWarnings("deprecation") + public void setChecked(boolean isChecked) { + if (isChecked) { + mFontLayout.setVisibility(View.VISIBLE); + mCheckImg.setImageDrawable(getResources().getDrawable(BoxingResHelper.getMediaCheckedRes())); + } else { + mFontLayout.setVisibility(View.GONE); + mCheckImg.setImageDrawable(getResources().getDrawable(BoxingResHelper.getMediaUncheckedRes())); + } + } + +} diff --git a/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/SpacesItemDecoration.java b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/SpacesItemDecoration.java new file mode 100644 index 0000000..f8a8712 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/java/com/bilibili/boxing_impl/view/SpacesItemDecoration.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 Bilibili + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bilibili.boxing_impl.view; + +import android.graphics.Rect; +import android.view.View; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; + +/** + * @author ChenSL + */ +public class SpacesItemDecoration extends RecyclerView.ItemDecoration { + private int mSpace; + private int mSpanCount; + private int mRadixX; + private int mItemCountInLastLine; + private int mOldItemCount = -1; + + public SpacesItemDecoration(int space) { + this(space, 1); + } + + public SpacesItemDecoration(int space, int spanCount) { + this.mSpace = space; + this.mSpanCount = spanCount; + this.mRadixX = space / spanCount; + } + + @Override + public void getItemOffsets(Rect outRect, View view, final RecyclerView parent, RecyclerView.State state) { + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); + final int sumCount = state.getItemCount(); + final int position = params.getViewLayoutPosition(); + final int spanSize; + final int index; + + if (params instanceof GridLayoutManager.LayoutParams) { + GridLayoutManager.LayoutParams gridParams = (GridLayoutManager.LayoutParams) params; + spanSize = gridParams.getSpanSize(); + index = gridParams.getSpanIndex(); + + if ((position == 0 || mOldItemCount != sumCount) && mSpanCount > 1) { + int countInLine = 0; + int spanIndex; + + for (int tempPosition = sumCount - mSpanCount; tempPosition < sumCount; tempPosition++) { + spanIndex = ((GridLayoutManager) parent.getLayoutManager()).getSpanSizeLookup().getSpanIndex(tempPosition, mSpanCount); + countInLine = spanIndex == 0 ? 1 : countInLine + 1; + } + mItemCountInLastLine = countInLine; + if (mOldItemCount != sumCount) { + mOldItemCount = sumCount; + if (position != 0) { + parent.post(new Runnable() { + @Override + public void run() { + parent.invalidateItemDecorations(); + } + }); + } + } + } + } else if (params instanceof StaggeredGridLayoutManager.LayoutParams) { + spanSize = ((StaggeredGridLayoutManager.LayoutParams) params).isFullSpan() ? mSpanCount : 1; + index = ((StaggeredGridLayoutManager.LayoutParams) params).getSpanIndex(); + } else { + spanSize = 1; + index = 0; + } + + if (spanSize < 1 || index < 0 || spanSize > mSpanCount) { + return; + } + + outRect.left = mSpace - mRadixX * index; + outRect.right = mRadixX + mRadixX * (index + spanSize - 1); + + if (mSpanCount == 1 && position == sumCount - 1) { + outRect.bottom = mSpace; + } else if (position >= sumCount - mItemCountInLastLine && position < sumCount) { + outRect.bottom = mSpace; + } + outRect.top = mSpace; + } +} \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/anim/boxing_fade_in.xml b/lib_media_selector/src/boxing-impl/res/anim/boxing_fade_in.xml new file mode 100644 index 0000000..9d20081 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/anim/boxing_fade_in.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/anim/boxing_fade_out.xml b/lib_media_selector/src/boxing-impl/res/anim/boxing_fade_out.xml new file mode 100644 index 0000000..61acd2b --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/anim/boxing_fade_out.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_broken_image.png b/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_broken_image.png new file mode 100644 index 0000000..8f88112 Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_broken_image.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_check_black.png b/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_check_black.png new file mode 100644 index 0000000..161d210 Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_check_black.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_checked.png b/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_checked.png new file mode 100644 index 0000000..ac25f2b Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-hdpi/ic_boxing_checked.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_broken_image.png b/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_broken_image.png new file mode 100644 index 0000000..9b4fc93 Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_broken_image.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_check_black.png b/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_check_black.png new file mode 100644 index 0000000..0404a7b Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_check_black.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_checked.png b/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_checked.png new file mode 100644 index 0000000..2134f7a Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-xhdpi/ic_boxing_checked.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_broken_image.png b/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_broken_image.png new file mode 100644 index 0000000..5e5c68e Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_broken_image.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_check_black.png b/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_check_black.png new file mode 100644 index 0000000..958363c Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_check_black.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_checked.png b/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_checked.png new file mode 100644 index 0000000..0d81030 Binary files /dev/null and b/lib_media_selector/src/boxing-impl/res/drawable-xxhdpi/ic_boxing_checked.png differ diff --git a/lib_media_selector/src/boxing-impl/res/drawable/selector_boxing_btn_solid.xml b/lib_media_selector/src/boxing-impl/res/drawable/selector_boxing_btn_solid.xml new file mode 100644 index 0000000..78a1590 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/drawable/selector_boxing_btn_solid.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/drawable/shape_boxing_popup_background.xml b/lib_media_selector/src/boxing-impl/res/drawable/shape_boxing_popup_background.xml new file mode 100644 index 0000000..385980c --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/drawable/shape_boxing_popup_background.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/drawable/shape_boxing_unchecked.xml b/lib_media_selector/src/boxing-impl/res/drawable/shape_boxing_unchecked.xml new file mode 100644 index 0000000..8ea147e --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/drawable/shape_boxing_unchecked.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/layout/activity_boxing.xml b/lib_media_selector/src/boxing-impl/res/layout/activity_boxing.xml new file mode 100644 index 0000000..4e93eaa --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/layout/activity_boxing.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/lib_media_selector/src/boxing-impl/res/layout/activity_boxing_bottom_sheet.xml b/lib_media_selector/src/boxing-impl/res/layout/activity_boxing_bottom_sheet.xml new file mode 100644 index 0000000..a211f5f --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/layout/activity_boxing_bottom_sheet.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib_media_selector/src/boxing-impl/res/layout/activity_boxing_view.xml b/lib_media_selector/src/boxing-impl/res/layout/activity_boxing_view.xml new file mode 100644 index 0000000..8ef5e06 --- /dev/null +++ b/lib_media_selector/src/boxing-impl/res/layout/activity_boxing_view.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + +