第一次提交

This commit is contained in:
被淹死的鱼
2026-03-11 18:26:29 +08:00
commit cd3b53759e
8532 changed files with 522078 additions and 0 deletions

1
uikit/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

45
uikit/build.gradle Normal file
View File

@@ -0,0 +1,45 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
viewBinding {
enabled = true
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//noinspection GradleCompatible
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
implementation group: 'com.squareup.picasso', name: 'picasso', version: '2.5.2'
// https://mvnrepository.com/artifact/com.github.bumptech.glide/glide
implementation group: 'com.github.bumptech.glide', name: 'glide', version: '4.9.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation group: 'com.facebook.fresco', name: 'fresco', version: '1.8.1'
implementation project(path: ':base')
}

21
uikit/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
package com.fengliyan.uikit;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.caivideo.uikit.test", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fengliyan.uikit" >
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<application android:allowBackup="true" android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:configChanges="orientation|screenSize"
android:name="com.fengliyan.uikit.photopicker.MultiImageSelectorActivity" />
</application>
</manifest>

View File

@@ -0,0 +1,213 @@
package com.fengliyan.uikit;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.appcompat.widget.AppCompatEditText;
public class ContentWithSpaceEditText extends AppCompatEditText {
public static final int TYPE_PHONE = 0;
public static final int TYPE_BANK_CARD = 1;
public static final int TYPE_ID_CARD = 2;
private int start, count,before;
private int contentType;
private int maxLength = 50;
private String digits;
public ContentWithSpaceEditText(Context context) {
this(context, null);
}
public ContentWithSpaceEditText(Context context, AttributeSet attrs) {
super(context, attrs);
parseAttributeSet(context, attrs);
}
public ContentWithSpaceEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
parseAttributeSet(context, attrs);
}
private void parseAttributeSet(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ContentWithSpaceEditText, 0, 0);
contentType = ta.getInt(R.styleable.ContentWithSpaceEditText_input_type, 0);
// 必须调用recycle
ta.recycle();
initType();
setSingleLine();
addTextChangedListener(watcher);
}
private void initType(){
if (contentType == TYPE_PHONE) {
maxLength = 13;
digits = "0123456789 ";
setInputType(InputType.TYPE_CLASS_NUMBER);
} else if (contentType == TYPE_BANK_CARD) {
maxLength = 31;
digits = "0123456789 ";
setInputType(InputType.TYPE_CLASS_NUMBER);
} else if (contentType == TYPE_ID_CARD) {
maxLength = 21;
digits = null;
setInputType(InputType.TYPE_CLASS_TEXT);
}
setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
}
@Override
public void setInputType(int type) {
if (contentType == TYPE_PHONE || contentType == TYPE_BANK_CARD) {
type = InputType.TYPE_CLASS_NUMBER;
}else if(contentType == TYPE_ID_CARD){
type = InputType.TYPE_CLASS_TEXT;
}
super.setInputType(type);
/* 非常重要:setKeyListener要在setInputType后面调用否则无效。*/
if(!TextUtils.isEmpty(digits)) {
setKeyListener(DigitsKeyListener.getInstance(digits));
}
}
/**
* 设置内容的类型
* @param contentType 类型
*/
public void setContentType(int contentType) {
this.contentType = contentType;
initType();
}
private TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
ContentWithSpaceEditText.this.start = start;
ContentWithSpaceEditText.this.before = before;
ContentWithSpaceEditText.this.count = count;
}
@Override
public void afterTextChanged(Editable s) {
if (s == null) {
return;
}
//判断是否是在中间输入,需要重新计算
boolean isMiddle = (start + count) < (s.length());
//在末尾输入时,是否需要加入空格
boolean isNeedSpace = false;
if (!isMiddle && isSpace(s.length())) {
isNeedSpace = true;
}
if (isMiddle || isNeedSpace || count > 1) {
String newStr = s.toString();
newStr = newStr.replace(" ", "");
StringBuilder sb = new StringBuilder();
int spaceCount = 0;
for (int i = 0; i < newStr.length(); i++) {
sb.append(newStr.substring(i, i+1));
//如果当前输入的字符下一位为空格(i+1+1+spaceCount)因为i是从0开始计算的所以一开始的时候需要先加1
if(isSpace(i + 2 + spaceCount)){
sb.append(" ");
spaceCount += 1;
}
}
removeTextChangedListener(watcher);
s.replace(0, s.length(),sb);
//如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
if (!isMiddle || count > 1) {
setSelection(s.length() <= maxLength ? s.length() : maxLength);
} else {
//如果是删除
if (count == 0) {
//如果删除时,光标停留在空格的前面,光标则要往前移一位
if (isSpace(start - before + 1)) {
setSelection((start - before) > 0 ? start - before : 0);
} else {
setSelection((start - before + 1) > s.length() ? s.length() : (start - before + 1));
}
}
//如果是增加
else {
if (isSpace(start - before + count)) {
setSelection((start + count - before + 1) < s.length() ? (start + count - before + 1) : s.length());
} else {
setSelection(start + count - before);
}
}
}
addTextChangedListener(watcher);
}
}
};
public String getTextWithoutSpace() {
return super.getText().toString().replace(" ", "");
}
public boolean checkTextRight(){
String text = getTextWithoutSpace();
//这里做个简单的内容判断
if (contentType == TYPE_PHONE) {
if (TextUtils.isEmpty(text)) {
showShort(getContext(), "手机号不能为空,请输入正确的手机号");
} else if (text.length() < 11) {
showShort(getContext(), "手机号不足11位请输入正确的手机号");
} else {
return true;
}
} else if (contentType == TYPE_BANK_CARD) {
if (TextUtils.isEmpty(text)) {
showShort(getContext(), "银行卡号不能为空,请输入正确的银行卡号");
} else if (text.length() < 14) {
showShort(getContext(), "银行卡号位数不正确,请输入正确的银行卡号");
} else {
return true;
}
} else if (contentType == TYPE_ID_CARD) {
if (TextUtils.isEmpty(text)) {
showShort(getContext(), "SFZ号不能为空请输入正确的SFZ号");
} else if (text.length() < 18) {
showShort(getContext(), "SFZ号不正确请输入正确的SFZ号");
} else {
return true;
}
}
return false;
}
private void showShort(Context context,String msg){
Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
private boolean isSpace(int length) {
if (contentType == TYPE_PHONE) {
return isSpacePhone(length);
} else if (contentType == TYPE_BANK_CARD) {
return isSpaceCard(length);
} else if (contentType == TYPE_ID_CARD) {
return isSpaceIDCard(length);
}
return false;
}
private boolean isSpacePhone(int length) {
return length >= 4 && (length == 4 || (length + 1) % 5 == 0);
}
private boolean isSpaceCard(int length) {
return length % 5 == 0;
}
private boolean isSpaceIDCard(int length) {
return length > 6 && (length == 7 || (length - 2) % 5 == 0);
}
}

View File

@@ -0,0 +1,37 @@
package com.fengliyan.uikit;
import android.content.Context;
import android.content.pm.PackageManager;
/**
* Created by chenqihong on 2017/1/17.
*/
public class UiUtils {
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 检查权限是否同意
* @param permission 权限名称
* @return true 具有该权限
*/
public static boolean checkPermission(Context context, String permission) {
return context.getPackageManager().checkPermission(permission, context.getPackageName()) == PackageManager
.PERMISSION_GRANTED;
}
}

View File

@@ -0,0 +1,36 @@
package com.fengliyan.uikit.adboard;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.viewpager.widget.ViewPager;
import com.fengliyan.uikit.R;
/**
* Created by abby on 2016/12/12.
*/
public class AdPages {
private ViewPager mAdvPager;
private ViewGroup mGroup;
private View mConvertView;
private AdPagesManager mManager;
public AdPages(Context context){
mConvertView = View.inflate(context, R.layout.ad_pager_board, null);
mAdvPager = (ViewPager)mConvertView.findViewById(R.id.ad_pager);
mGroup = (ViewGroup)mConvertView.findViewById(R.id.view_group);
mManager = new AdPagesManager(mAdvPager, mGroup, context);
}
public AdPages addImageById(int id){
mManager.addImageViewById(id);
return this;
}
public View convertView(){
mManager.manage();
return mConvertView;
}
}

View File

@@ -0,0 +1,261 @@
package com.fengliyan.uikit.adboard;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.view.SimpleDraweeView;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ChenQihong on 2016/11/9.
*/
public class AdPagesManager {
private View[] mViews = null;
private ViewPager mAdvPager = null;
private ViewGroup mGroup = null;
private List<View> mAdvPics = new ArrayList<View>();
private AtomicInteger mWhat = new AtomicInteger(0);
private boolean isContinue = true;
private Context mContext;
private ImageClickListener mListener;
public AdPagesManager(ViewPager advPager, ViewGroup group, Context context){
this.mAdvPager = advPager;
this.mContext = context;
this.mGroup = group;
}
public void addImageViewById(int id){
ImageView img = new ImageView(mContext);
img.setBackgroundResource(id);
mAdvPics.add(img);
}
public void addImageViewByDrawable(Drawable d){
ImageView img = new ImageView(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
img.setBackground(d);
}else{
img.setBackgroundDrawable(d);
}
mAdvPics.add(img);
}
public void addVideoView(View view){
mAdvPics.add(view);
view.setClickable(true);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mListener.onClick(v, mAdvPics.indexOf(v));
}
});
}
public void addImageViewByUrl(String url){
SimpleDraweeView img = new SimpleDraweeView(mContext);
Uri uri = Uri.parse(url);
img.setImageURI(uri);
img.getHierarchy().setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP);
img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onClick(view, mAdvPics.indexOf(view));
}
});
mAdvPics.add(img);
}
public interface ImageClickListener{
void onClick(View v, int position);
}
public void setOnImageClickListener(ImageClickListener listener){
mListener = listener;
}
public boolean hasImages(){
return !mAdvPics.isEmpty();
}
public void manage(){
mViews = new ImageView[mAdvPics.size()];
for (int i = 0; i < mAdvPics.size(); i++) {
ImageView imageView = new ImageView(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(10, 10);
params.setMargins(12, 0, 0, 0);
imageView.setLayoutParams(params);
imageView.setPadding(12, 12, 12, 12);
mViews[i] = imageView;
if (i == 0) {
mViews[i]
.setBackgroundResource(R.drawable.shape_banner_index_focus);
} else {
mViews[i]
.setBackgroundResource(R.drawable.shape_banner_index_inactived);
}
mGroup.addView(mViews[i]);
}
mAdvPager.setAdapter(new AdvAdapter(mAdvPics));
mAdvPager.setOnPageChangeListener(new GuidePageChangeListener());
mAdvPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
isContinue = false;
break;
case MotionEvent.ACTION_UP:
isContinue = true;
break;
default:
isContinue = true;
break;
}
return false;
}
});
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// while (true) {
// if (isContinue) {
// viewHandler.sendEmptyMessage(mWhat.get());
// whatOption();
// }
// }
// }
//
// }).start();
}
private void whatOption() {
mWhat.incrementAndGet();
if (mWhat.get() > mViews.length - 1) {
mWhat.getAndAdd(-4);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
private final Handler viewHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mAdvPager.setCurrentItem(msg.what);
super.handleMessage(msg);
}
};
private final class GuidePageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int arg0) {
mWhat.getAndSet(arg0);
for (int i = 0; i < mViews.length; i++) {
mViews[arg0]
.setBackgroundResource(R.drawable.shape_banner_index_focus);
if (arg0 != i) {
mViews[i]
.setBackgroundResource(R.drawable.shape_banner_index_inactived);
}
}
}
}
private final class AdvAdapter extends PagerAdapter {
private List<View> views = null;
public AdvAdapter(List<View> views) {
this.views = views;
}
@Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView(views.get(arg1));
}
@Override
public void finishUpdate(View arg0) {
}
@Override
public int getCount() {
return views.size();
}
@Override
public Object instantiateItem(View arg0, int arg1) {
((ViewPager) arg0).addView(views.get(arg1), 0);
return views.get(arg1);
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void startUpdate(View arg0) {
}
}
public void clear(){
if(null != mAdvPics){
mAdvPics.clear();
mGroup.removeAllViews();
}
}
}

View File

@@ -0,0 +1,283 @@
package com.fengliyan.uikit.bottomedit;
import android.content.Context;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Created by abby on 2017/1/12.
*/
public class BottomEdit extends LinearLayout implements View.OnClickListener{
private Context mContext;
private EditText mEditView;
private View mEditLayout;
private Button mSubmitButton;
private List<Fragment> mFragmentList = new ArrayList<>();
private View mBottomFragment;
private View mEmojiView;
private View mPhotoView;
private View mGiftView;
private View mVideoView;
private View mBottomView;
private View mFunctionalBottom;
private Fragment mAddFragment;
private InputMethodManager mInputMethodManager;
private SubmitButtonClickListener mSubmitButtonClickedListener;
private FunctionButtonClickListener mFunctionButtonClickedListener;
public interface SubmitButtonClickListener{
void onSubmitButtonClicked(String message);
}
public interface FunctionButtonClickListener{
void onClicked(int index);
}
public void setSubmitButtonClickedListener(SubmitButtonClickListener listener){
mSubmitButtonClickedListener = listener;
}
public void setFunctionButtonClickListener(FunctionButtonClickListener listener){
mFunctionButtonClickedListener = listener;
}
public BottomEdit(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rootView = inflater.inflate(R.layout.bottom_edit_view, this, true);
mEditView = (EditText) rootView.findViewById(R.id.edit_edit);
mEditLayout = rootView.findViewById(R.id.edit_edit_layout);
mBottomView = rootView.findViewById(R.id.edit_layout);
mBottomFragment = rootView.findViewById(R.id.bottom_fragment_layout);
mEmojiView = rootView.findViewById(R.id.edit_emoji);
mPhotoView = rootView.findViewById(R.id.edit_image);
mGiftView = rootView.findViewById(R.id.edit_gift);
mVideoView = rootView.findViewById(R.id.edit_video);
mSubmitButton = rootView.findViewById(R.id.eidt_submit_button);
mFunctionalBottom = rootView.findViewById(R.id.edit_functional_bottom);
mEmojiView.setOnClickListener(this);
mPhotoView.setOnClickListener(this);
mGiftView.setOnClickListener(this);
mVideoView.setOnClickListener(this);
mSubmitButton.setOnClickListener(this);
mEditView.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if(b){
dismissBottomFragment();
}
}
});
checkEditEmpty();
mEditView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
checkEditEmpty();
}
});
mInputMethodManager = (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
//View editLayout = rootView.findViewById(R.id.edit_layout);
//controlKeyboardLayout(rootView, editLayout);
}
private void checkEditEmpty(){
if(TextUtils.isEmpty(mEditView.getText().toString())){
mSubmitButton.setEnabled(false);
}else{
mSubmitButton.setEnabled(true);
}
}
public void setFragment(Fragment fragment){
FragmentActivity fragmentActivity = (FragmentActivity)mContext;
try {
if (!fragment.isAdded()) {
fragmentActivity.getSupportFragmentManager().
beginTransaction().
add(R.id.detail_fragment_layout, fragment).
show(fragment).
commitAllowingStateLoss();
} else {
fragmentActivity.getSupportFragmentManager().
beginTransaction().
show(fragment).
commitAllowingStateLoss();
}
}catch (IllegalStateException e){
fragmentActivity.getSupportFragmentManager().
beginTransaction().
show(fragment).
commit();
}
}
public void setBottomFragmentHeight(int height){
LinearLayout.LayoutParams params = (LayoutParams) mBottomFragment.getLayoutParams();
params.height = height;
mBottomFragment.setLayoutParams(params);
invalidate();
}
public BottomEdit addBottomFragment(Fragment fragment){
mFragmentList.add(fragment);
return this;
}
public void setBottomFragment(Fragment fragment){
FragmentActivity fragmentActivity = (FragmentActivity)mContext;
try {
if(null != mAddFragment){
fragmentActivity.getSupportFragmentManager().
beginTransaction().remove(mAddFragment).commit();
}
if (!fragment.isAdded()) {
fragmentActivity.getSupportFragmentManager().
beginTransaction().
add(R.id.bottom_fragment_layout, fragment).
show(fragment).
commitAllowingStateLoss();
} else {
fragmentActivity.getSupportFragmentManager().
beginTransaction().
show(fragment).
commitAllowingStateLoss();
}
mAddFragment = fragment;
}catch (IllegalStateException e){
fragmentActivity.getSupportFragmentManager().
beginTransaction().
show(fragment).
commit();
}
}
public EditText getEditText(){
return mEditView;
}
public void showBottomFragment(){
mBottomFragment.setVisibility(VISIBLE);
}
public void dismissBottomFragment(){
FragmentActivity fragmentActivity = (FragmentActivity)mContext;
Iterator<Fragment> iterator = mFragmentList.iterator();
while (iterator.hasNext()) {
Fragment fragment = iterator.next();
fragmentActivity.getSupportFragmentManager().
beginTransaction().remove(fragment).commitAllowingStateLoss();
}
//LinearLayout.LayoutParams params = (LayoutParams) mBottomFragment.getLayoutParams();
//params.height = 0;
//mBottomFragment.setLayoutParams(params);
}
public void dismissAllBottom(){
mEditView.clearFocus();
dismissBottomFragment();
mInputMethodManager.hideSoftInputFromWindow(mEditView.getWindowToken(),0);
}
public void dimissBottomFunction(){
dismissAllBottom();
mFunctionalBottom.setVisibility(GONE);
}
public void dismissInput(){
mEditView.clearFocus();
mBottomView.setVisibility(GONE);
mInputMethodManager.hideSoftInputFromWindow(mEditView.getWindowToken(),0);
}
public void showInput(){
mBottomView.setVisibility(VISIBLE);
mEditView.clearFocus();
mInputMethodManager.showSoftInput(mEditView, 0);
}
public void dimissVideo(){
mVideoView.setVisibility(GONE);
}
@Override
public void onClick(View view) {
if(view == mEmojiView){
showBottomFragment();
Fragment fragment = mFragmentList.get(0);
setBottomFragment(fragment);
mEditView.clearFocus();
mInputMethodManager.hideSoftInputFromWindow(mEditView.getWindowToken(),0);
if(null != mFunctionButtonClickedListener){
mFunctionButtonClickedListener.onClicked(2);
}
}else if(view == mSubmitButton){
if(null != mSubmitButtonClickedListener){
mSubmitButtonClickedListener.onSubmitButtonClicked(mEditView.getText().toString());
}
}else if(view == mPhotoView){
mEditView.clearFocus();
if(null != mFunctionButtonClickedListener){
mFunctionButtonClickedListener.onClicked(1);
}
}else if(view == mGiftView){
showBottomFragment();
Fragment fragment = mFragmentList.get(1);
setBottomFragment(fragment);
mEditView.clearFocus();
mInputMethodManager.hideSoftInputFromWindow(mEditView.getWindowToken(),0);
if(null != mFunctionButtonClickedListener){
mFunctionButtonClickedListener.onClicked(3);
}
}else if(view == mVideoView){
mEditView.clearFocus();
if(null != mFunctionButtonClickedListener){
mFunctionButtonClickedListener.onClicked(4);
}
}
}
}

View File

@@ -0,0 +1,93 @@
package com.fengliyan.uikit.bottompost;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.fengliyan.uikit.R;
/**
* Created by abby on 2017/6/25.
*/
public class BottomPost extends LinearLayout {
private Context mContext;
private ImageView mAddPhoto;
private Button mPostButton;
public BottomPost(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rootView = inflater.inflate(R.layout.bottom_post_view, this, true);
View postLayout = rootView.findViewById(R.id.post_layout);
mPostButton = (Button) rootView.findViewById(R.id.bottom_post_button);
mAddPhoto = (ImageView) rootView.findViewById(R.id.bottom_add_photos);
controlKeyboardLayout(postLayout, postLayout);
}
public void setFragment(Fragment fragment) {
FragmentActivity fragmentActivity = (FragmentActivity) mContext;
try {
if (!fragment.isAdded()) {
fragmentActivity.getSupportFragmentManager().
beginTransaction().
add(R.id.post_fragment_layout, fragment).
show(fragment).
commit();
} else {
fragmentActivity.getSupportFragmentManager().
beginTransaction().
show(fragment).
commit();
}
}catch (IllegalStateException e){
fragmentActivity.getSupportFragmentManager().
beginTransaction().
show(fragment).
commit();
}
}
public void controlKeyboardLayout(final View root,
final View editLayout) {
root.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect rect=new Rect();
root.getWindowVisibleDisplayFrame(rect);
int rootInVisibleHeigh=root.getRootView().getHeight()-rect.bottom;
if (rootInVisibleHeigh > 100) {
int[] location = new int[2];
editLayout.getLocationInWindow(location);
int srollHeight = (location[1] + editLayout.getHeight()) - rect.bottom;
if(srollHeight != 0) {
//root.scrollTo(0, srollHeight);
root.setPadding(0, 0, 0 , srollHeight);
}
} else {
//root.scrollTo(0, 0);
root.setPadding(0, 0, 0, 0);
}
}
});
}
}

View File

@@ -0,0 +1,351 @@
package com.fengliyan.uikit.bottomtab;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.facebook.drawee.view.SimpleDraweeView;
import com.fengliyan.base.base.utils.NotificationBadgesUtils;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.UiUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Created by ChenQihong on 2016/12/13.
*/
public class BottomTab extends LinearLayout implements View.OnClickListener {
private Context mContext;
private FrameLayout mFragmentLayout;
public List<Fragment> mFragmentList = new ArrayList<>();
private int mCurrentPosition;
private TabClickListener mListener;
private VideoClickListener mVideoClickListener;
private View mMainLayout;
private View mDynamicLayout;
private View mSettingsLayout;
private View mNewsLayout;
private View mVideoLogo;
private View mVideoText;
private View mVideoLayout;
private View mVideoCancel;
// private View mVideoPoint;
// private View mVideoFrame;
private TextView mNewsCountView;
private TextView mSettingCountView;
private int mTabIndex;
private boolean isMale;
private SimpleDraweeView bottom_tab_news_image;
private String avatar;
@Override
public void onClick(View view) {
if (view == mMainLayout) { //首页
change(0);
mTabIndex = 0;
} else if (view == mDynamicLayout) { //动态
change(1);
mTabIndex = 1;
} else if (view == mSettingsLayout) { //我的
change(4);
mTabIndex = 4;
} else if (view == mNewsLayout) { //消息
change(3);
mTabIndex = 3;
} else if (view == mVideoLayout) { //榜单
// if(null != mVideoClickListener){
// mVideoClickListener.onVideoClick();
// }
change(2);
mTabIndex = 2;
}
}
public interface TabClickListener {
void onClick(int position, int currentPosition);
}
public interface VideoClickListener {
void onVideoClick();
}
public void setVideoClickListener(VideoClickListener listener) {
mVideoClickListener = listener;
}
// public void startFlick(){
// View view = mVideoPoint;
// Animation alphaAnimation = new AlphaAnimation( 1, 0.4f );
// alphaAnimation.setDuration( 300 );
// alphaAnimation.setInterpolator( new LinearInterpolator( ) );
// alphaAnimation.setRepeatCount( Animation.INFINITE );
// alphaAnimation.setRepeatMode( Animation.REVERSE );
// view.startAnimation( alphaAnimation );
// }
// public void stopFlick(){
// mVideoPoint.clearAnimation();
// }
public Fragment getFragment(int index) {
return mFragmentList.get(index);
}
public void setOnTabClickListener(TabClickListener listener) {
this.mListener = listener;
}
public BottomTab(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.bottom_tab_view1, this, true);
mMainLayout = findViewById(R.id.bottom_tab_main_layout);
mMainLayout.setOnClickListener(this);
mDynamicLayout = findViewById(R.id.bottom_tab_dynamic_layout);
mDynamicLayout.setOnClickListener(this);
mNewsLayout = findViewById(R.id.bottom_tab_news_layout);
mNewsLayout.setOnClickListener(this);
mSettingsLayout = findViewById(R.id.bottom_tab_settings_layout);
mSettingsLayout.setOnClickListener(this);
mNewsCountView = findViewById(R.id.bottom_tab_news_count);
mSettingCountView = findViewById(R.id.bottom_tab_settings_count);
mVideoLayout = findViewById(R.id.bottom_tab_live_layout);
// mVideoLogo = findViewById(R.id.bottom_tab_live_image);
bottom_tab_news_image = findViewById(R.id.bottom_tab_news_image);
bottom_tab_news_image.setImageResource(R.drawable.ic_tab_news1);
// mVideoPoint = findViewById(R.id.main_fragment_point);
// mVideoFrame = findViewById(R.id.main_fragment_video);
mVideoLayout.setOnClickListener(this);
change(0);
}
public void enableVideo() {
if (isMale) {
return;
}
mVideoLayout.setVisibility(VISIBLE);
mVideoLogo.setVisibility(VISIBLE);
// mVideoPoint.setVisibility(VISIBLE);
// startFlick();
}
public void disableVideo() {
if (isMale) {
return;
}
mVideoLayout.setVisibility(VISIBLE);
mVideoLogo.setVisibility(VISIBLE);
// stopFlick();
// mVideoPoint.setVisibility(GONE);
}
public void dismissVideoButton() {
mVideoLayout.setVisibility(GONE);
mVideoLogo.setVisibility(GONE);
// mVideoPoint.setVisibility(GONE);
}
public void addTab(Fragment fragment) {
mFragmentList.add(fragment);
if (fragment == null || fragment.isAdded()) {
return;
}
FragmentActivity activity = (FragmentActivity) mContext;
activity.getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment_layout, fragment, fragment.getClass().getName()).hide(fragment).commit();
}
public void change(int position) {
if (!(mContext instanceof FragmentActivity)) {
return;
}
if (position < mFragmentList.size()) {
Fragment targetFragment = mFragmentList.get(position);
Fragment currentFragment = mFragmentList.get(mCurrentPosition);
FragmentActivity activity = (FragmentActivity) mContext;
if (activity.getSupportFragmentManager() != null) {
try {
activity.getSupportFragmentManager().beginTransaction().hide(currentFragment).show(targetFragment).commitAllowingStateLoss();
} catch (IllegalStateException e) {
activity.getSupportFragmentManager().beginTransaction().hide(currentFragment).show(targetFragment).commitAllowingStateLoss();
}
}
}
if (null != mListener) {
mListener.onClick(position, mCurrentPosition);
}
tabStyleChange(position, mCurrentPosition);
mCurrentPosition = position;
}
private void tabStyleChange(int targetPosition, int currentPosition) {
if (0 == targetPosition) {
((TextView) findViewById(R.id.bottom_tab_main_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text_selected));
((ImageView) findViewById(R.id.bottom_tab_main_image)).setImageResource(R.drawable.ic_tab_home2);
setImageLayoutParams(findViewById(R.id.bottom_tab_main_image), 28);
}
if (1 == targetPosition) {
((TextView) findViewById(R.id.bottom_tab_dynamic_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text_selected));
((ImageView) findViewById(R.id.bottom_tab_dynamic_image)).setImageResource(R.drawable.ic_tab_dynamic2);
setImageLayoutParams(findViewById(R.id.bottom_tab_dynamic_image), 28);
} else if (3 == targetPosition) {
((TextView) findViewById(R.id.bottom_tab_news_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text_selected));
if (TextUtils.isEmpty(avatar)) {
bottom_tab_news_image.setImageResource(R.drawable.ic_tab_news2);
setImageLayoutParams(bottom_tab_news_image, 28);
} else {
setImageLayoutParams(bottom_tab_news_image, 28);
}
} else if (4 == targetPosition) {
((TextView) findViewById(R.id.bottom_tab_settings_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text_selected));
((ImageView) findViewById(R.id.bottom_tab_settings_image)).setImageResource(R.drawable.ic_tab_my2);
setImageLayoutParams(findViewById(R.id.bottom_tab_settings_image), 28);
} else if (2 == targetPosition) {
((TextView) findViewById(R.id.bottom_tab_ranking_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text_selected));
((ImageView) findViewById(R.id.main_fragment_video)).setImageResource(R.drawable.ic_tab_ranking2);
setImageLayoutParams(findViewById(R.id.main_fragment_video), 28);
}
if (targetPosition == currentPosition) {
return;
}
if (0 == currentPosition) {
((TextView) findViewById(R.id.bottom_tab_main_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text));
((ImageView) findViewById(R.id.bottom_tab_main_image)).setImageResource(R.drawable.ic_tab_home1);
setImageLayoutParams(findViewById(R.id.bottom_tab_main_image), 24);
} else if (1 == currentPosition) {
((TextView) findViewById(R.id.bottom_tab_dynamic_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text));
((ImageView) findViewById(R.id.bottom_tab_dynamic_image)).setImageResource(R.drawable.ic_tab_dynamic1);
setImageLayoutParams(findViewById(R.id.bottom_tab_dynamic_image), 24);
} else if (3 == currentPosition) {
((TextView) findViewById(R.id.bottom_tab_news_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text));
if (TextUtils.isEmpty(avatar)) {
bottom_tab_news_image.setImageResource(R.drawable.ic_tab_news1);
setImageLayoutParams(bottom_tab_news_image, 24);
} else {
setImageLayoutParams(bottom_tab_news_image, 28);
}
} else if (4 == currentPosition) {
((TextView) findViewById(R.id.bottom_tab_settings_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text));
((ImageView) findViewById(R.id.bottom_tab_settings_image)).setImageResource(R.drawable.ic_tab_my1);
setImageLayoutParams(findViewById(R.id.bottom_tab_settings_image), 24);
} else if (2 == currentPosition) {
((TextView) findViewById(R.id.bottom_tab_ranking_text)).setTextColor(ContextCompat.getColor(mContext, R.color.zhimi_bottom_text));
((ImageView) findViewById(R.id.main_fragment_video)).setImageResource(R.drawable.ic_tab_ranking1);
setImageLayoutParams(findViewById(R.id.main_fragment_video), 24);
}
}
/**
* 设置图标大小
*
* @param view tab图标
* @param dp 大小
*/
private void setImageLayoutParams(View view, int dp) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.width = UiUtils.dip2px(mContext, dp);
layoutParams.height = UiUtils.dip2px(mContext, dp);
view.setLayoutParams(layoutParams);
}
public int getTabIndex() {
return mCurrentPosition;
}
public void setNewCount(int num, String avatar) {
this.avatar = avatar;
if (0 == num) {
mNewsCountView.setVisibility(GONE);
} else {
if (num > 99) {
mNewsCountView.setText("99");
} else {
mNewsCountView.setText(num + "");
}
mNewsCountView.setVisibility(VISIBLE);
}
Log.i("TAG", "setNewCount: ----------->" + avatar);
if (!TextUtils.isEmpty(avatar)) {
bottom_tab_news_image.setImageURI(avatar);
setImageLayoutParams(bottom_tab_news_image, 28);
} else {
if (3 == mCurrentPosition) {
bottom_tab_news_image.setImageResource(R.drawable.ic_tab_news2);
setImageLayoutParams(bottom_tab_news_image, 28);
} else {
bottom_tab_news_image.setImageResource(R.drawable.ic_tab_news1);
setImageLayoutParams(bottom_tab_news_image, 24);
}
}
// String manufacturer = Build.MANUFACTURER;
// if (manufacturer != null && manufacturer.length() > 0) {
// String phone_type = manufacturer.toLowerCase();
// if ("huawei".equals(phone_type)) {
// showHuaWeiDeskMark(num);
// }
// }
NotificationBadgesUtils.setCount(num, mContext);
}
/**
* @param number 显示桌面角标数量 0 则隐藏
* 显示华为角标
*/
private void showHuaWeiDeskMark(int number) {
try {
Bundle extra = new Bundle();
extra.putString("package", getContext().getPackageName());
extra.putString("class", "com.fengliyan.tianlesue.view.main.CoverActivity");
extra.putInt("badgenumber", number);
getContext().getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, extra);
} catch (Exception ignored) {
ignored.printStackTrace();
Log.e("err", "showHuaWeiDeskMark is Error");
}
}
public void setSettingsCount(int num) {
if (0 == num) {
mSettingCountView.setVisibility(GONE);
} else {
mSettingCountView.setText(num + "");
mSettingCountView.setVisibility(VISIBLE);
}
}
public void maleStyle() {
mVideoLayout.setVisibility(GONE);
mVideoLogo.setVisibility(GONE);
// mVideoPoint.setVisibility(GONE);
// mVideoFrame.setVisibility(GONE);
isMale = true;
}
}

View File

@@ -0,0 +1,27 @@
package com.fengliyan.uikit.crop;
import android.content.Intent;
import android.net.Uri;
/**
* Created by neuyuandaima on 2016/1/13.
*
* @deprecated Use {@link CropHelper} instead of it.
*/
public interface Crop {
/**
* 打开图库,由用户选择照片后裁剪
*/
void start();
/**
* 裁剪指定的资源
*
* @param uri 图片uri
*/
void start(Uri uri);
//展示结果
void onActivityResult(int requestCode, int resultCode, Intent data, CropCallback callback);
}

View File

@@ -0,0 +1,15 @@
package com.fengliyan.uikit.crop;
/**
* 图片裁剪回调
* Created by neuyuandaima on 2016/1/13.
*/
public interface CropCallback {
/**
* 裁剪后的回调
* @param path 当成功保存时返回图片路径否则为null
*/
void OnReceiveBitmap(String path);
}

View File

@@ -0,0 +1,43 @@
package com.fengliyan.uikit.crop;
import android.app.Activity;
import androidx.fragment.app.Fragment;
/**
* Created by neuyuandaima on 2016/1/13.
* 剪切工厂类
*
* @deprecated Use {@link CropHelper} instead of it.
*/
public class CropFactory {
/**
* 传入activity的Contact
*
* @param activity 传入参数
* @return 返回Contact接口对象
*/
public static Crop newCrop(Activity activity) {
return new CropImpl(activity);
}
/**
* 传入fragment的Contact
*
* @param fragment 传入参数
* @return 返回Contact接口对象
*/
public static Crop newCrop(Fragment fragment) {
return new CropImpl(fragment);
}
/**
* 传入fragment的Contact
*
* @param fragment 传入参数
* @return 返回Contact接口对象
*/
public static Crop newCrop(android.app.Fragment fragment) {
return new CropImpl(fragment);
}
}

View File

@@ -0,0 +1,118 @@
package com.fengliyan.uikit.crop;
import android.Manifest.permission;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.MediaStore;
import androidx.fragment.app.Fragment;
import com.fengliyan.uikit.UiUtils;
import com.fengliyan.uikit.crop.app.CropActivity;
import com.fengliyan.uikit.crop.app.CropExtras;
import java.io.File;
import java.io.IOException;
/**
* 使用裁剪功能的帮助类在CropHelper的构造中需传入裁剪结果的回调当用户取消等返回null裁剪保存后返回图片的路径。
* Created by yangjinbo on 2016/2/26.
*/
public class CropHelper {
private static final int REQ_CROP = 231;
private Activity mActivity;
public CropHelper(Activity activity) {
if (activity == null) {
throw new IllegalArgumentException("activity cannot be null!");
}
mActivity = activity;
}
/**
* 开始裁剪。将在CropHelper构造方法的CropCallback回调方法中返回图片路径。
* 需要在调用的activity的{@link Activity#onActivityResult(int, int, Intent)}
* 或者fragment中的{@link Fragment#onActivityResult(int, int, Intent)}中调用
* {@link #handleResult(int, int, Intent, CropCallback)}处理返回的结果。
*
* @param uri 裁剪源图片如果为null会打开相册由用户自己选择。
* @see #start(Object, Uri)
*/
public void start(Uri uri) {
start(null, uri);
}
/**
* 开始裁剪图片
*
* @param fragment onActivityResult(int, int, Intent)回调所在的fragment
* @param uri 图片源文件
* @see #start(Uri)
*/
public void start(final Object fragment, final Uri uri) {
if ((fragment != null) && !(fragment instanceof Fragment) && !(fragment instanceof android.app.Fragment)) {
throw new IllegalArgumentException("if fragment isn't null," +
" fragment must be android.support.v4.app.Fragment or android.app.Fragment! ");
}
if (!UiUtils.checkPermission(mActivity, permission.WRITE_EXTERNAL_STORAGE)) {
return;
}
Intent intent = new Intent(mActivity, CropActivity.class);
intent.setData(uri);
setIntentExtra(intent);
if (fragment != null) {
if (fragment instanceof Fragment) {
((Fragment) fragment).startActivityForResult(intent, REQ_CROP);
} else {
((android.app.Fragment) fragment).startActivityForResult(intent, REQ_CROP);
}
} else {
mActivity.startActivityForResult(intent, REQ_CROP);
}
}
private void setIntentExtra(Intent intent) {
intent.putExtra(CropExtras.KEY_ASPECT_X, 1);
intent.putExtra(CropExtras.KEY_ASPECT_Y, 1);
intent.putExtra(CropExtras.KEY_OUTPUT_FORMAT, "jpg");
intent.putExtra(CropExtras.KEY_RETURN_DATA, false);
File file = createImageFile();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
}
private File createImageFile() {
try {
return File.createTempFile("temp", ".jpg", mActivity.getExternalCacheDir());
} catch (IOException e) {
return new File(mActivity.getCacheDir().getAbsolutePath()
+ "/" + System.currentTimeMillis() + ".jpg");
}
}
/**
* 处理裁剪后的结果在Activity或者Fragment中的onActivityResult(int, int, Intent)调用
*
* @return true 已处理false 未处理
*/
public boolean handleResult(int requestCode, int resultCode, Intent data, CropCallback cropCallback) {
if (requestCode != REQ_CROP) {
return false;
}
if (resultCode != Activity.RESULT_OK || data == null) {
cropCallback.OnReceiveBitmap(null);
} else {
cropCallback.OnReceiveBitmap(data.getData().getPath());
}
return true;
}
}

View File

@@ -0,0 +1,116 @@
package com.fengliyan.uikit.crop;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.MediaStore;
import androidx.fragment.app.Fragment;
import com.fengliyan.uikit.crop.app.CropActivity;
import com.fengliyan.uikit.crop.app.CropExtras;
import java.io.File;
import java.io.IOException;
/**
* Created by neuyuandaima on 2016/1/13.\
* crop实现类
*
* @deprecated Use {@link CropHelper} instead of it.
*/
public class CropImpl implements Crop {
private static final int SELECT_PICTURE = 6019; // request code for picker 和用户的特征码区分开
private static final int CROP_PICTURE = 6020; // request code for crop 和用户的特征码区分开
//持有activity
private Activity mActivity;
//持有fragment
private Fragment mSupportFragment;
//持有fragment
private android.app.Fragment mFragment;
//构造器
public CropImpl(Activity activity) {
mActivity = activity;
}
public CropImpl(Fragment fragment) {
mSupportFragment = fragment;
mActivity = mSupportFragment.getActivity();
}
public CropImpl(android.app.Fragment fragment) {
mFragment = fragment;
mActivity = mFragment.getActivity();
}
@Override
public void start() {
Intent intent = new Intent(mActivity, CropActivity.class);
setIntentExtra(intent);
if (mFragment != null) {
mFragment.startActivityForResult(intent, SELECT_PICTURE);
} else if (mSupportFragment != null) {
mSupportFragment.startActivityForResult(intent, SELECT_PICTURE);
} else {
mActivity.startActivityForResult(intent, SELECT_PICTURE);
}
}
private void setIntentExtra(Intent intent) {
intent.putExtra(CropExtras.KEY_ASPECT_X, 1);
intent.putExtra(CropExtras.KEY_ASPECT_Y, 1);
intent.putExtra(CropExtras.KEY_OUTPUT_FORMAT, "jpg");
intent.putExtra(CropExtras.KEY_RETURN_DATA, false);
File file = newImgFile();
if (file == null) {
file = new File(mActivity.getCacheDir().getAbsolutePath()
+ "/" + System.currentTimeMillis() + ".jpg");
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
}
@Override
public void start(Uri uri) {
Intent intent = new Intent(CropActivity.CROP_ACTION, uri, mActivity, CropActivity.class);
setIntentExtra(intent);
if (mFragment != null) {
mFragment.startActivityForResult(intent, CROP_PICTURE);
} else if (mSupportFragment != null) {
mSupportFragment.startActivityForResult(intent, CROP_PICTURE);
} else {
mActivity.startActivityForResult(intent, CROP_PICTURE);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data, CropCallback callback) {
if (requestCode != CROP_PICTURE && requestCode != SELECT_PICTURE) {
return;
}
if (resultCode != Activity.RESULT_OK) {
callback.OnReceiveBitmap(null);
return;
}
if (data == null) {
callback.OnReceiveBitmap(null);
} else {
callback.OnReceiveBitmap(data.getData().getPath());
}
}
private File newImgFile() {
try {
return File.createTempFile("temp", ".jpg", mActivity.getExternalCacheDir());
} catch (IOException e) {
return null;
}
}
}

View File

@@ -0,0 +1,366 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import java.util.Arrays;
/**
* Maintains invariant that inner rectangle is constrained to be within the
* outer, rotated rectangle.
*/
public class BoundedRect {
private float rot;
private RectF outer;
private RectF inner;
private float[] innerRotated;
public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
rot = rotation;
outer = new RectF(outerRect);
inner = new RectF(innerRect);
innerRotated = CropMath.getCornersFromRect(inner);
rotateInner();
if (!isConstrained())
reconstrain();
}
public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
rot = rotation;
outer = new RectF(outerRect);
inner = new RectF(innerRect);
innerRotated = CropMath.getCornersFromRect(inner);
rotateInner();
if (!isConstrained())
reconstrain();
}
public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
rot = rotation;
outer.set(outerRect);
inner.set(innerRect);
innerRotated = CropMath.getCornersFromRect(inner);
rotateInner();
if (!isConstrained())
reconstrain();
}
/**
* Sets inner, and re-constrains it to fit within the rotated bounding rect.
*/
public void setInner(RectF newInner) {
if (inner.equals(newInner))
return;
inner = newInner;
innerRotated = CropMath.getCornersFromRect(inner);
rotateInner();
if (!isConstrained())
reconstrain();
}
/**
* Sets rotation, and re-constrains inner to fit within the rotated bounding rect.
*/
public void setRotation(float rotation) {
if (rotation == rot)
return;
rot = rotation;
innerRotated = CropMath.getCornersFromRect(inner);
rotateInner();
if (!isConstrained())
reconstrain();
}
public void setToInner(RectF r) {
r.set(inner);
}
public void setToOuter(RectF r) {
r.set(outer);
}
public RectF getInner() {
return new RectF(inner);
}
public RectF getOuter() {
return new RectF(outer);
}
/**
* Tries to move the inner rectangle by (dx, dy). If this would cause it to leave
* the bounding rectangle, snaps the inner rectangle to the edge of the bounding
* rectangle.
*/
public void moveInner(float dx, float dy) {
Matrix m0 = getInverseRotMatrix();
RectF translatedInner = new RectF(inner);
translatedInner.offset(dx, dy);
float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner);
float[] outerCorners = CropMath.getCornersFromRect(outer);
m0.mapPoints(translatedInnerCorners);
float[] correction = {
0, 0
};
// find correction vectors for corners that have moved out of bounds
for (int i = 0; i < translatedInnerCorners.length; i += 2) {
float correctedInnerX = translatedInnerCorners[i] + correction[0];
float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
float[] badCorner = {
correctedInnerX, correctedInnerY
};
float[] nearestSide = CropMath.closestSide(badCorner, outerCorners);
float[] correctionVec =
Utils.shortestVectorFromPointToLine(badCorner, nearestSide);
correction[0] += correctionVec[0];
correction[1] += correctionVec[1];
}
}
for (int i = 0; i < translatedInnerCorners.length; i += 2) {
float correctedInnerX = translatedInnerCorners[i] + correction[0];
float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
float[] correctionVec = {
correctedInnerX, correctedInnerY
};
CropMath.getEdgePoints(outer, correctionVec);
correctionVec[0] -= correctedInnerX;
correctionVec[1] -= correctedInnerY;
correction[0] += correctionVec[0];
correction[1] += correctionVec[1];
}
}
// Set correction
for (int i = 0; i < translatedInnerCorners.length; i += 2) {
float correctedInnerX = translatedInnerCorners[i] + correction[0];
float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
// update translated corners with correction vectors
translatedInnerCorners[i] = correctedInnerX;
translatedInnerCorners[i + 1] = correctedInnerY;
}
innerRotated = translatedInnerCorners;
// reconstrain to update inner
reconstrain();
}
/**
* Attempts to resize the inner rectangle. If this would cause it to leave
* the bounding rect, clips the inner rectangle to fit.
*/
public void resizeInner(RectF newInner) {
Matrix m = getRotMatrix();
Matrix m0 = getInverseRotMatrix();
float[] outerCorners = CropMath.getCornersFromRect(outer);
m.mapPoints(outerCorners);
float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
RectF ret = new RectF(newInner);
for (int i = 0; i < newInnerCorners.length; i += 2) {
float[] c = {
newInnerCorners[i], newInnerCorners[i + 1]
};
float[] c0 = Arrays.copyOf(c, 2);
m0.mapPoints(c0);
if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
float[] outerSide = CropMath.closestSide(c, outerCorners);
float[] pathOfCorner = {
newInnerCorners[i], newInnerCorners[i + 1],
oldInnerCorners[i], oldInnerCorners[i + 1]
};
float[] p = Utils.lineIntersect(pathOfCorner, outerSide);
if (p == null) {
// lines are parallel or not well defined, so don't resize
p = new float[2];
p[0] = oldInnerCorners[i];
p[1] = oldInnerCorners[i + 1];
}
// relies on corners being in same order as method
// getCornersFromRect
switch (i) {
case 0:
case 1:
ret.left = (p[0] > ret.left) ? p[0] : ret.left;
ret.top = (p[1] > ret.top) ? p[1] : ret.top;
break;
case 2:
case 3:
ret.right = (p[0] < ret.right) ? p[0] : ret.right;
ret.top = (p[1] > ret.top) ? p[1] : ret.top;
break;
case 4:
case 5:
ret.right = (p[0] < ret.right) ? p[0] : ret.right;
ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
break;
case 6:
case 7:
ret.left = (p[0] > ret.left) ? p[0] : ret.left;
ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
break;
default:
break;
}
}
}
float[] retCorners = CropMath.getCornersFromRect(ret);
m0.mapPoints(retCorners);
innerRotated = retCorners;
// reconstrain to update inner
reconstrain();
}
/**
* Attempts to resize the inner rectangle. If this would cause it to leave
* the bounding rect, clips the inner rectangle to fit while maintaining
* aspect ratio.
*/
public void fixedAspectResizeInner(RectF newInner) {
Matrix m = getRotMatrix();
Matrix m0 = getInverseRotMatrix();
float aspectW = inner.width();
float aspectH = inner.height();
float aspRatio = aspectW / aspectH;
float[] corners = CropMath.getCornersFromRect(outer);
m.mapPoints(corners);
float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
// find fixed corner
int fixed = -1;
if (inner.top == newInner.top) {
if (inner.left == newInner.left)
fixed = 0; // top left
else if (inner.right == newInner.right)
fixed = 2; // top right
} else if (inner.bottom == newInner.bottom) {
if (inner.right == newInner.right)
fixed = 4; // bottom right
else if (inner.left == newInner.left)
fixed = 6; // bottom left
}
// no fixed corner, return without update
if (fixed == -1)
return;
float widthSoFar = newInner.width();
int moved = -1;
for (int i = 0; i < newInnerCorners.length; i += 2) {
float[] c = {
newInnerCorners[i], newInnerCorners[i + 1]
};
float[] c0 = Arrays.copyOf(c, 2);
m0.mapPoints(c0);
if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
moved = i;
if (moved == fixed)
continue;
float[] l2 = CropMath.closestSide(c, corners);
float[] l1 = {
newInnerCorners[i], newInnerCorners[i + 1],
oldInnerCorners[i], oldInnerCorners[i + 1]
};
float[] p = Utils.lineIntersect(l1, l2);
if (p == null) {
// lines are parallel or not well defined, so set to old
// corner
p = new float[2];
p[0] = oldInnerCorners[i];
p[1] = oldInnerCorners[i + 1];
}
// relies on corners being in same order as method
// getCornersFromRect
float fixed_x = oldInnerCorners[fixed];
float fixed_y = oldInnerCorners[fixed + 1];
float newWidth = Math.abs(fixed_x - p[0]);
float newHeight = Math.abs(fixed_y - p[1]);
newWidth = Math.max(newWidth, aspRatio * newHeight);
if (newWidth < widthSoFar)
widthSoFar = newWidth;
}
}
float heightSoFar = widthSoFar / aspRatio;
RectF ret = new RectF(inner);
if (fixed == 0) {
ret.right = ret.left + widthSoFar;
ret.bottom = ret.top + heightSoFar;
} else if (fixed == 2) {
ret.left = ret.right - widthSoFar;
ret.bottom = ret.top + heightSoFar;
} else if (fixed == 4) {
ret.left = ret.right - widthSoFar;
ret.top = ret.bottom - heightSoFar;
} else if (fixed == 6) {
ret.right = ret.left + widthSoFar;
ret.top = ret.bottom - heightSoFar;
}
float[] retCorners = CropMath.getCornersFromRect(ret);
m0.mapPoints(retCorners);
innerRotated = retCorners;
// reconstrain to update inner
reconstrain();
}
// internal methods
private boolean isConstrained() {
for (int i = 0; i < 8; i += 2) {
if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1]))
return false;
}
return true;
}
private void reconstrain() {
// innerRotated has been changed to have incorrect values
CropMath.getEdgePoints(outer, innerRotated);
Matrix m = getRotMatrix();
float[] unrotated = Arrays.copyOf(innerRotated, 8);
m.mapPoints(unrotated);
inner = CropMath.trapToRect(unrotated);
}
private void rotateInner() {
Matrix m = getInverseRotMatrix();
m.mapPoints(innerRotated);
}
private Matrix getRotMatrix() {
Matrix m = new Matrix();
m.setRotate(rot, outer.centerX(), outer.centerY());
return m;
}
private Matrix getInverseRotMatrix() {
Matrix m = new Matrix();
m.setRotate(-rot, outer.centerX(), outer.centerY());
return m;
}
}

View File

@@ -0,0 +1,720 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.app.ActionBar;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import com.fengliyan.uikit.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Activity for cropping an image.
*/
public class CropActivity extends Activity {
private static final String TAG = "CropActivity";
public static final String CROP_ACTION = "com.android.camera.action.CROP";
private CropExtras mCropExtras = null;
private LoadBitmapTask mLoadBitmapTask = null;
private int mOutputX = 0;
private int mOutputY = 0;
private Bitmap mOriginalBitmap = null;
private RectF mOriginalBounds = null;
private int mOriginalRotation = 0;
private Uri mSourceUri = null;
private CropView mCropView = null;
private View mSaveButton = null;
private boolean finalIOGuard = false;
private static final int SELECT_PICTURE = 1; // request code for picker
private static final int DEFAULT_COMPRESS_QUALITY = 90;
/**
* The maximum bitmap size we allow to be returned through the intent.
* Intents have a maximum of 1MB in total size. However, the Bitmap seems to
* have some overhead to hit so that we go way below the limit here to make
* sure the intent stays below 1MB.We should consider just returning a byte
* array instead of a Bitmap instance to avoid overhead.
*/
public static final int MAX_BMAP_IN_INTENT = 750000;
// Flags
private static final int DO_SET_WALLPAPER = 1;
private static final int DO_RETURN_DATA = 1 << 1;
private static final int DO_EXTRA_OUTPUT = 1 << 2;
private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setResult(RESULT_CANCELED, new Intent());
mCropExtras = getExtrasFromIntent(intent);
if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
setContentView(R.layout.muna_activity_crop);
mCropView = (CropView) findViewById(R.id.cropView);
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
actionBar.setCustomView(R.layout.muna_filtershow_actionbar);
View mSaveButton = actionBar.getCustomView();
mSaveButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mSourceUri == null) {
setResult(RESULT_CANCELED, new Intent());
done();
} else {
startFinishOutput();
}
}
});
}
if (intent.getData() != null) {
mSourceUri = intent.getData();
startLoadBitmap(mSourceUri);
} else {
pickImage();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public void onResume() {
super.onResume();
}
private void enableSave(boolean enable) {
if (mSaveButton != null) {
mSaveButton.setEnabled(enable);
}
}
@Override
protected void onDestroy() {
if (mLoadBitmapTask != null) {
mLoadBitmapTask.cancel(false);
}
super.onDestroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mCropView.configChanged();
}
/**
* Opens a selector in Gallery to chose an image for use when none was given
* in the CROP intent.
*/
private void pickImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
SELECT_PICTURE);
}
/**
* Callback for pickImage().
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_CANCELED && requestCode == SELECT_PICTURE) {
setResult(RESULT_CANCELED);
finish();
}
if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
mSourceUri = data.getData();
startLoadBitmap(mSourceUri);
}
}
/**
* Gets screen size metric.
*/
private int getScreenImageSize() {
DisplayMetrics outMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
}
/**
* Method that loads a bitmap in an async task.
*/
private void startLoadBitmap(Uri uri) {
if (uri != null) {
enableSave(false);
final View loading = findViewById(R.id.loading);
loading.setVisibility(View.VISIBLE);
mLoadBitmapTask = new LoadBitmapTask();
mLoadBitmapTask.execute(uri);
} else {
cannotLoadImage();
done();
}
}
/**
* Method called on UI thread with loaded bitmap.
*/
private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
final View loading = findViewById(R.id.loading);
loading.setVisibility(View.GONE);
mOriginalBitmap = bitmap;
mOriginalBounds = bounds;
mOriginalRotation = orientation;
if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
mCropView.initialize(bitmap, imgBounds, imgBounds, orientation);
if (mCropExtras != null) {
int aspectX = mCropExtras.getAspectX();
int aspectY = mCropExtras.getAspectY();
mOutputX = mCropExtras.getOutputX();
mOutputY = mCropExtras.getOutputY();
if (mOutputX > 0 && mOutputY > 0) {
mCropView.applyAspect(mOutputX, mOutputY);
}
float spotX = mCropExtras.getSpotlightX();
float spotY = mCropExtras.getSpotlightY();
if (spotX > 0 && spotY > 0) {
mCropView.setWallpaperSpotlight(spotX, spotY);
}
if (aspectX > 0 && aspectY > 0) {
mCropView.applyAspect(aspectX, aspectY);
}
}
enableSave(true);
} else {
Log.w(TAG, "could not load image for cropping");
cannotLoadImage();
setResult(RESULT_CANCELED, new Intent());
done();
}
}
/**
* Display toast for image loading failure.
*/
private void cannotLoadImage() {
CharSequence text = getString(R.string.cannot_load_image);
Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
toast.show();
}
/**
* AsyncTask for loading a bitmap into memory.
*
* @see #startLoadBitmap(Uri)
*/
private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
int mBitmapSize;
Context mContext;
Rect mOriginalBounds;
int mOrientation;
public LoadBitmapTask() {
mBitmapSize = getScreenImageSize();
mContext = getApplicationContext();
mOriginalBounds = new Rect();
mOrientation = 0;
}
@Override
protected Bitmap doInBackground(Uri... params) {
Uri uri = params[0];
Bitmap bmap = Utils.loadConstrainedBitmap(uri, mContext, mBitmapSize,
mOriginalBounds, false);
mOrientation = Utils.getMetadataRotation(mContext, uri);
return bmap;
}
@Override
protected void onPostExecute(Bitmap result) {
doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
}
}
protected void startFinishOutput() {
if (finalIOGuard) {
return;
} else {
finalIOGuard = true;
}
enableSave(false);
Uri destinationUri = null;
int flags = 0;
if (mOriginalBitmap != null && mCropExtras != null) {
if (mCropExtras.getExtraOutput() != null) {
destinationUri = mCropExtras.getExtraOutput();
if (destinationUri != null) {
flags |= DO_EXTRA_OUTPUT;
}
}
if (mCropExtras.getSetAsWallpaper()) {
flags |= DO_SET_WALLPAPER;
}
if (mCropExtras.getReturnData()) {
flags |= DO_RETURN_DATA;
}
}
if (flags == 0) {
destinationUri = Utils.makeAndInsertUri(this, mSourceUri);
if (destinationUri != null) {
flags |= DO_EXTRA_OUTPUT;
}
}
if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
RectF crop = getBitmapCrop(photo);
startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
photo, mOriginalBounds,
(mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
return;
}
setResult(RESULT_CANCELED, new Intent());
done();
return;
}
private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
int rotation) {
if (cropBounds == null || photoBounds == null || currentBitmap == null
|| currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
|| cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
|| photoBounds.height() == 0) {
return; // fail fast
}
if ((flags & FLAG_CHECK) == 0) {
return; // no output options
}
if ((flags & DO_SET_WALLPAPER) != 0) {
Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
}
final View loading = findViewById(R.id.loading);
loading.setVisibility(View.VISIBLE);
BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
ioTask.execute(currentBitmap);
}
private void doneBitmapIO(boolean success, Intent intent) {
final View loading = findViewById(R.id.loading);
loading.setVisibility(View.GONE);
if (success) {
setResult(RESULT_OK, intent);
} else {
setResult(RESULT_CANCELED, intent);
}
done();
}
private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
private final WallpaperManager mWPManager;
InputStream mInStream = null;
OutputStream mOutStream = null;
String mOutputFormat = null;
Uri mOutUri = null;
Uri mInUri = null;
int mFlags = 0;
RectF mCrop = null;
RectF mPhoto = null;
RectF mOrig = null;
Intent mResultIntent = null;
int mRotation = 0;
// Helper to setup input stream
private void regenerateInputStream() {
if (mInUri == null) {
Log.w(TAG, "cannot read original file, no input URI given");
} else {
Utils.closeSilently(mInStream);
try {
mInStream = getContentResolver().openInputStream(mInUri);
} catch (FileNotFoundException e) {
Log.w(TAG, "cannot read file: " + mInUri.toString(), e);
}
}
}
public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
int outputX, int outputY) {
mOutputFormat = outputFormat;
mOutStream = null;
mOutUri = destUri;
mInUri = sourceUri;
mFlags = flags;
mCrop = cropBounds;
mPhoto = photoBounds;
mOrig = originalBitmapBounds;
mWPManager = WallpaperManager.getInstance(getApplicationContext());
mResultIntent = new Intent();
mRotation = (rotation < 0) ? -rotation : rotation;
mRotation %= 360;
mRotation = 90 * (int) (mRotation / 90); // now mRotation is a multiple of 90
mOutputX = outputX;
mOutputY = outputY;
if ((flags & DO_EXTRA_OUTPUT) != 0) {
if (mOutUri == null) {
Log.w(TAG, "cannot write file, no output URI given");
} else {
try {
mOutStream = getContentResolver().openOutputStream(mOutUri);
} catch (FileNotFoundException e) {
Log.w(TAG, "cannot write file: " + mOutUri.toString(), e);
}
}
}
if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
regenerateInputStream();
}
}
@Override
protected Boolean doInBackground(Bitmap... params) {
boolean failure = false;
Bitmap img = params[0];
try {
// Set extra for crop bounds
if (mCrop != null && mPhoto != null && mOrig != null) {
RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
Matrix m = new Matrix();
m.setRotate(mRotation);
m.mapRect(trueCrop);
if (trueCrop != null) {
Rect rounded = new Rect();
trueCrop.roundOut(rounded);
mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
}
}
// Find the small cropped bitmap that is returned in the intent
if ((mFlags & DO_RETURN_DATA) != 0) {
assert (img != null);
Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
if (ret != null) {
ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
}
if (ret == null) {
Log.w(TAG, "could not downsample bitmap to return in data");
failure = true;
} else {
if (mRotation > 0) {
Matrix m = new Matrix();
m.setRotate(mRotation);
Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
ret.getHeight(), m, true);
if (tmp != null) {
ret = tmp;
}
}
mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
}
}
// Do the large cropped bitmap and/or set the wallpaper
if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
// Find crop bounds (scaled to original image size)
RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
if (trueCrop == null) {
Log.w(TAG, "cannot find crop for full size image");
failure = true;
return false;
}
Rect roundedTrueCrop = new Rect();
trueCrop.roundOut(roundedTrueCrop);
if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
Log.w(TAG, "crop has bad values for full size image");
failure = true;
return false;
}
// Attempt to open a region decoder
BitmapRegionDecoder decoder = null;
try {
decoder = BitmapRegionDecoder.newInstance(mInStream, true);
} catch (IOException e) {
Log.w(TAG, "cannot open region decoder for file: " + mInUri.toString(), e);
}
Bitmap crop = null;
if (decoder != null) {
// Do region decoding to get crop bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
crop = decoder.decodeRegion(roundedTrueCrop, options);
decoder.recycle();
}
if (crop == null) {
// BitmapRegionDecoder has failed, try to crop in-memory
regenerateInputStream();
Bitmap fullSize = null;
if (mInStream != null) {
fullSize = BitmapFactory.decodeStream(mInStream);
}
if (fullSize != null) {
crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
roundedTrueCrop.top, roundedTrueCrop.width(),
roundedTrueCrop.height());
}
}
if (crop == null) {
Log.w(TAG, "cannot decode file: " + mInUri.toString());
failure = true;
return false;
}
if (mOutputX > 0 && mOutputY > 0) {
Matrix m = new Matrix();
RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
if (mRotation > 0) {
m.setRotate(mRotation);
m.mapRect(cropRect);
}
RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
m.preRotate(mRotation);
Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
(int) returnRect.height(), Bitmap.Config.ARGB_8888);
if (tmp != null) {
Canvas c = new Canvas(tmp);
c.drawBitmap(crop, m, new Paint());
crop = tmp;
}
} else if (mRotation > 0) {
Matrix m = new Matrix();
m.setRotate(mRotation);
Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
crop.getHeight(), m, true);
if (tmp != null) {
crop = tmp;
}
}
// Get output compression format
CompressFormat cf =
convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
// If we only need to output to a URI, compress straight to file
if (mFlags == DO_EXTRA_OUTPUT) {
if (mOutStream == null
|| !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
Log.w(TAG, "failed to compress bitmap to file: " + mOutUri.toString());
failure = true;
} else {
mResultIntent.setData(mOutUri);
}
} else {
// Compress to byte array
ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
// If we need to output to a Uri, write compressed
// bitmap out
if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
if (mOutStream == null) {
Log.w(TAG,
"failed to compress bitmap to file: " + mOutUri.toString());
failure = true;
} else {
try {
mOutStream.write(tmpOut.toByteArray());
mResultIntent.setData(mOutUri);
} catch (IOException e) {
Log.w(TAG,
"failed to compress bitmap to file: "
+ mOutUri.toString(), e);
failure = true;
}
}
}
// If we need to set to the wallpaper, set it
if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
if (mWPManager == null) {
Log.w(TAG, "no wallpaper manager");
failure = true;
} else {
try {
mWPManager.setStream(new ByteArrayInputStream(tmpOut
.toByteArray()));
} catch (IOException e) {
Log.w(TAG, "cannot write stream to wallpaper", e);
failure = true;
}
}
}
} else {
Log.w(TAG, "cannot compress bitmap");
failure = true;
}
}
}
} catch (OutOfMemoryError e) {
failure = true;
}
return !failure; // True if any of the operations failed
}
@Override
protected void onPostExecute(Boolean result) {
Utils.closeSilently(mOutStream);
Utils.closeSilently(mInStream);
doneBitmapIO(result.booleanValue(), mResultIntent);
}
}
private void done() {
finish();
}
protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
if (crop == null) {
return null;
}
Rect intCrop = new Rect();
crop.roundOut(intCrop);
return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
intCrop.height());
}
protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
}
int shifts = 0;
int size = CropMath.getBitmapSize(image);
while (size > max_size) {
shifts++;
size /= 4;
}
Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
image.getHeight() >> shifts, true);
if (ret == null) {
return null;
}
// Handle edge case for rounding.
if (CropMath.getBitmapSize(ret) > max_size) {
return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
}
return ret;
}
/**
* Gets the crop extras from the intent, or null if none exist.
*/
protected static CropExtras getExtrasFromIntent(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
extras.getBoolean(CropExtras.KEY_SCALE, true) &&
extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
extras.getInt(CropExtras.KEY_ASPECT_X, 0),
extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
(Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
}
return null;
}
protected static CompressFormat convertExtensionToCompressFormat(String extension) {
return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
}
protected static String getFileExtension(String requestFormat) {
String outputFormat = (requestFormat == null)
? "jpg"
: requestFormat;
outputFormat = outputFormat.toLowerCase();
return (outputFormat.equals("png") || outputFormat.equals("gif"))
? "png" // We don't support gif compression.
: "jpg";
}
private RectF getBitmapCrop(RectF imageBounds) {
RectF crop = mCropView.getCrop();
RectF photo = mCropView.getPhoto();
if (crop == null || photo == null) {
Log.w(TAG, "could not get crop");
return null;
}
RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
return scaledCrop;
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
public abstract class CropDrawingUtils {
public static void drawRuleOfThird(Canvas canvas, RectF bounds) {
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.argb(128, 255, 255, 255));
p.setStrokeWidth(2);
float stepX = bounds.width() / 3.0f;
float stepY = bounds.height() / 3.0f;
float x = bounds.left + stepX;
float y = bounds.top + stepY;
for (int i = 0; i < 2; i++) {
canvas.drawLine(x, bounds.top, x, bounds.bottom, p);
x += stepX;
}
for (int j = 0; j < 2; j++) {
canvas.drawLine(bounds.left, y, bounds.right, y, p);
y += stepY;
}
}
public static void drawCropRect(Canvas canvas, RectF bounds) {
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.WHITE);
p.setStrokeWidth(3);
canvas.drawRect(bounds, p);
}
public static void drawShade(Canvas canvas, RectF bounds) {
int w = canvas.getWidth();
int h = canvas.getHeight();
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setColor(Color.BLACK & 0x88000000);
RectF r = new RectF();
r.set(0,0,w,bounds.top);
canvas.drawRect(r, p);
r.set(0,bounds.top,bounds.left,h);
canvas.drawRect(r, p);
r.set(bounds.left,bounds.bottom,w,h);
canvas.drawRect(r, p);
r.set(bounds.right,bounds.top,w,bounds.bottom);
canvas.drawRect(r, p);
}
public static void drawIndicator(Canvas canvas, Drawable indicator, int indicatorSize,
float centerX, float centerY) {
int left = (int) centerX - indicatorSize / 2;
int top = (int) centerY - indicatorSize / 2;
indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
indicator.draw(canvas);
}
public static void drawIndicators(Canvas canvas, Drawable cropIndicator, int indicatorSize,
RectF bounds, boolean fixedAspect, int selection) {
boolean notMoving = (selection == CropObject.MOVE_NONE);
if (fixedAspect) {
if ((selection == CropObject.TOP_LEFT) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.top);
}
if ((selection == CropObject.TOP_RIGHT) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.top);
}
if ((selection == CropObject.BOTTOM_LEFT) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.bottom);
}
if ((selection == CropObject.BOTTOM_RIGHT) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.bottom);
}
} else {
if (((selection & CropObject.MOVE_TOP) != 0) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.top);
}
if (((selection & CropObject.MOVE_BOTTOM) != 0) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.bottom);
}
if (((selection & CropObject.MOVE_LEFT) != 0) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.centerY());
}
if (((selection & CropObject.MOVE_RIGHT) != 0) || notMoving) {
drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.centerY());
}
}
}
public static void drawWallpaperSelectionFrame(Canvas canvas, RectF cropBounds, float spotX,
float spotY, Paint p, Paint shadowPaint) {
float sx = cropBounds.width() * spotX;
float sy = cropBounds.height() * spotY;
float cx = cropBounds.centerX();
float cy = cropBounds.centerY();
RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
float temp = sx;
sx = sy;
sy = temp;
RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
canvas.save();
canvas.clipRect(cropBounds);
canvas.clipRect(r1, Region.Op.DIFFERENCE);
canvas.clipRect(r2, Region.Op.DIFFERENCE);
canvas.drawPaint(shadowPaint);
canvas.restore();
Path path = new Path();
path.moveTo(r1.left, r1.top);
path.lineTo(r1.right, r1.top);
path.moveTo(r1.left, r1.top);
path.lineTo(r1.left, r1.bottom);
path.moveTo(r1.left, r1.bottom);
path.lineTo(r1.right, r1.bottom);
path.moveTo(r1.right, r1.top);
path.lineTo(r1.right, r1.bottom);
path.moveTo(r2.left, r2.top);
path.lineTo(r2.right, r2.top);
path.moveTo(r2.right, r2.top);
path.lineTo(r2.right, r2.bottom);
path.moveTo(r2.left, r2.bottom);
path.lineTo(r2.right, r2.bottom);
path.moveTo(r2.left, r2.top);
path.lineTo(r2.left, r2.bottom);
canvas.drawPath(path, p);
}
public static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds) {
canvas.drawRect(outerBounds.left, outerBounds.top, innerBounds.right, innerBounds.top, p);
canvas.drawRect(innerBounds.right, outerBounds.top, outerBounds.right, innerBounds.bottom,
p);
canvas.drawRect(innerBounds.left, innerBounds.bottom, outerBounds.right,
outerBounds.bottom, p);
canvas.drawRect(outerBounds.left, innerBounds.top, innerBounds.left, outerBounds.bottom, p);
}
public static Matrix getBitmapToDisplayMatrix(RectF imageBounds, RectF displayBounds) {
Matrix m = new Matrix();
CropDrawingUtils.setBitmapToDisplayMatrix(m, imageBounds, displayBounds);
return m;
}
public static boolean setBitmapToDisplayMatrix(Matrix m, RectF imageBounds,
RectF displayBounds) {
m.reset();
return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
}
public static boolean setImageToScreenMatrix(Matrix dst, RectF image,
RectF screen, int rotation) {
RectF rotatedImage = new RectF();
dst.setRotate(rotation, image.centerX(), image.centerY());
if (!dst.mapRect(rotatedImage, image)) {
return false; // fails for rotations that are not multiples of 90
// degrees
}
boolean rToR = dst.setRectToRect(rotatedImage, screen, Matrix.ScaleToFit.CENTER);
boolean rot = dst.preRotate(rotation, image.centerX(), image.centerY());
return rToR && rot;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.net.Uri;
public class CropExtras {
public static final String KEY_CROPPED_RECT = "cropped-rect";
public static final String KEY_OUTPUT_X = "outputX";
public static final String KEY_OUTPUT_Y = "outputY";
public static final String KEY_SCALE = "scale";
public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
public static final String KEY_ASPECT_X = "aspectX";
public static final String KEY_ASPECT_Y = "aspectY";
public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
public static final String KEY_RETURN_DATA = "return-data";
public static final String KEY_DATA = "data";
public static final String KEY_SPOTLIGHT_X = "spotlightX";
public static final String KEY_SPOTLIGHT_Y = "spotlightY";
public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
public static final String KEY_OUTPUT_FORMAT = "outputFormat";
private int mOutputX = 0;
private int mOutputY = 0;
private boolean mScaleUp = true;
private int mAspectX = 0;
private int mAspectY = 0;
private boolean mSetAsWallpaper = false;
private boolean mReturnData = false;
private Uri mExtraOutput = null;
private String mOutputFormat = null;
private boolean mShowWhenLocked = false;
private float mSpotlightX = 0;
private float mSpotlightY = 0;
public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY,
boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat,
boolean showWhenLocked, float spotlightX, float spotlightY) {
mOutputX = outputX;
mOutputY = outputY;
mScaleUp = scaleUp;
mAspectX = aspectX;
mAspectY = aspectY;
mSetAsWallpaper = setAsWallpaper;
mReturnData = returnData;
mExtraOutput = extraOutput;
mOutputFormat = outputFormat;
mShowWhenLocked = showWhenLocked;
mSpotlightX = spotlightX;
mSpotlightY = spotlightY;
}
public CropExtras(CropExtras c) {
this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper,
c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked,
c.mSpotlightX, c.mSpotlightY);
}
public int getOutputX() {
return mOutputX;
}
public int getOutputY() {
return mOutputY;
}
public boolean getScaleUp() {
return mScaleUp;
}
public int getAspectX() {
return mAspectX;
}
public int getAspectY() {
return mAspectY;
}
public boolean getSetAsWallpaper() {
return mSetAsWallpaper;
}
public boolean getReturnData() {
return mReturnData;
}
public Uri getExtraOutput() {
return mExtraOutput;
}
public String getOutputFormat() {
return mOutputFormat;
}
public boolean getShowWhenLocked() {
return mShowWhenLocked;
}
public float getSpotlightX() {
return mSpotlightX;
}
public float getSpotlightY() {
return mSpotlightY;
}
}

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import java.util.Arrays;
public class CropMath {
/**
* Gets a float array of the 2D coordinates representing a rectangles
* corners.
* The order of the corners in the float array is:
* 0------->1
* ^ |
* | v
* 3<-------2
*
* @param r the rectangle to get the corners of
* @return the float array of corners (8 floats)
*/
public static float[] getCornersFromRect(RectF r) {
float[] corners = {
r.left, r.top,
r.right, r.top,
r.right, r.bottom,
r.left, r.bottom
};
return corners;
}
/**
* Returns true iff point (x, y) is within or on the rectangle's bounds.
* RectF's "contains" function treats points on the bottom and right bound
* as not being contained.
*
* @param r the rectangle
* @param x the x value of the point
* @param y the y value of the point
* @return
*/
public static boolean inclusiveContains(RectF r, float x, float y) {
return !(x > r.right || x < r.left || y > r.bottom || y < r.top);
}
/**
* Takes an array of 2D coordinates representing corners and returns the
* smallest rectangle containing those coordinates.
*
* @param array array of 2D coordinates
* @return smallest rectangle containing coordinates
*/
public static RectF trapToRect(float[] array) {
RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
for (int i = 1; i < array.length; i += 2) {
float x = array[i - 1];
float y = array[i];
r.left = (x < r.left) ? x : r.left;
r.top = (y < r.top) ? y : r.top;
r.right = (x > r.right) ? x : r.right;
r.bottom = (y > r.bottom) ? y : r.bottom;
}
r.sort();
return r;
}
/**
* If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
* image bound rectangle, clamps it to the edge of the rectangle.
*
* @param imageBound the rectangle to clamp edge points to.
* @param array an array of points to clamp to the rectangle, gets set to
* the clamped values.
*/
public static void getEdgePoints(RectF imageBound, float[] array) {
if (array.length < 2)
return;
for (int x = 0; x < array.length; x += 2) {
array[x] = Utils.clamp(array[x], imageBound.left, imageBound.right);
array[x + 1] = Utils.clamp(array[x + 1], imageBound.top, imageBound.bottom);
}
}
/**
* Takes a point and the corners of a rectangle and returns the two corners
* representing the side of the rectangle closest to the point.
*
* @param point the point which is being checked
* @param corners the corners of the rectangle
* @return two corners representing the side of the rectangle
*/
public static float[] closestSide(float[] point, float[] corners) {
int len = corners.length;
float oldMag = Float.POSITIVE_INFINITY;
float[] bestLine = null;
for (int i = 0; i < len; i += 2) {
float[] line = {
corners[i], corners[(i + 1) % len],
corners[(i + 2) % len], corners[(i + 3) % len]
};
float mag = Utils.vectorLength(
Utils.shortestVectorFromPointToLine(point, line));
if (mag < oldMag) {
oldMag = mag;
bestLine = line;
}
}
return bestLine;
}
/**
* Checks if a given point is within a rotated rectangle.
*
* @param point 2D point to check
* @param bound rectangle to rotate
* @param rot angle of rotation about rectangle center
* @return true if point is within rotated rectangle
*/
public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) {
Matrix m = new Matrix();
float[] p = Arrays.copyOf(point, 2);
m.setRotate(rot, bound.centerX(), bound.centerY());
Matrix m0 = new Matrix();
if (!m.invert(m0))
return false;
m0.mapPoints(p);
return inclusiveContains(bound, p[0], p[1]);
}
/**
* Checks if a given point is within a rotated rectangle.
*
* @param point 2D point to check
* @param rotatedRect corners of a rotated rectangle
* @param center center of the rotated rectangle
* @return true if point is within rotated rectangle
*/
public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) {
RectF unrotated = new RectF();
float angle = getUnrotated(rotatedRect, center, unrotated);
return pointInRotatedRect(point, unrotated, angle);
}
/**
* Resizes rectangle to have a certain aspect ratio (center remains
* stationary).
*
* @param r rectangle to resize
* @param w new width aspect
* @param h new height aspect
*/
public static void fixAspectRatio(RectF r, float w, float h) {
float scale = Math.min(r.width() / w, r.height() / h);
float centX = r.centerX();
float centY = r.centerY();
float hw = scale * w / 2;
float hh = scale * h / 2;
r.set(centX - hw, centY - hh, centX + hw, centY + hh);
}
/**
* Resizes rectangle to have a certain aspect ratio (center remains
* stationary) while constraining it to remain within the original rect.
*
* @param r rectangle to resize
* @param w new width aspect
* @param h new height aspect
*/
public static void fixAspectRatioContained(RectF r, float w, float h) {
float origW = r.width();
float origH = r.height();
float origA = origW / origH;
float a = w / h;
float finalW = origW;
float finalH = origH;
if (origA < a) {
finalH = origW / a;
r.top = r.centerY() - finalH / 2;
r.bottom = r.top + finalH;
} else {
finalW = origH * a;
r.left = r.centerX() - finalW / 2;
r.right = r.left + finalW;
}
}
/**
* Stretches/Scales/Translates photoBounds to match displayBounds, and
* and returns an equivalent stretched/scaled/translated cropBounds or null
* if the mapping is invalid.
* @param cropBounds cropBounds to transform
* @param photoBounds original bounds containing crop bounds
* @param displayBounds final bounds for crop
* @return the stretched/scaled/translated crop bounds that fit within displayBounds
*/
public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds,
RectF displayBounds) {
Matrix m = new Matrix();
m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL);
RectF trueCrop = new RectF(cropBounds);
if (!m.mapRect(trueCrop)) {
return null;
}
return trueCrop;
}
/**
* Returns the size of a bitmap in bytes.
* @param bmap bitmap whose size to check
* @return bitmap size in bytes
*/
public static int getBitmapSize(Bitmap bmap) {
return bmap.getRowBytes() * bmap.getHeight();
}
/**
* Constrains rotation to be in [0, 90, 180, 270] rounding down.
* @param rotation any rotation value, in degrees
* @return integer rotation in [0, 90, 180, 270]
*/
public static int constrainedRotation(float rotation) {
int r = (int) ((rotation % 360) / 90);
r = (r < 0) ? (r + 4) : r;
return r * 90;
}
private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
float dy = rotatedRect[1] - rotatedRect[3];
float dx = rotatedRect[0] - rotatedRect[2];
float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI);
Matrix m = new Matrix();
m.setRotate(-angle, center[0], center[1]);
float[] unrotatedRect = new float[rotatedRect.length];
m.mapPoints(unrotatedRect, rotatedRect);
unrotated.set(trapToRect(unrotatedRect));
return angle;
}
}

View File

@@ -0,0 +1,328 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.graphics.Rect;
import android.graphics.RectF;
public class CropObject {
private BoundedRect mBoundedRect;
private float mAspectWidth = 1;
private float mAspectHeight = 1;
private boolean mFixAspectRatio = false;
private float mRotation = 0;
private float mTouchTolerance = 45;
private float mMinSideSize = 20;
public static final int MOVE_NONE = 0;
// Sides
public static final int MOVE_LEFT = 1;
public static final int MOVE_TOP = 2;
public static final int MOVE_RIGHT = 4;
public static final int MOVE_BOTTOM = 8;
public static final int MOVE_BLOCK = 16;
// Corners
public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
private int mMovingEdges = MOVE_NONE;
public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
}
public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
}
public void resetBoundsTo(RectF inner, RectF outer) {
mBoundedRect.resetTo(0, outer, inner);
}
public void getInnerBounds(RectF r) {
mBoundedRect.setToInner(r);
}
public void getOuterBounds(RectF r) {
mBoundedRect.setToOuter(r);
}
public RectF getInnerBounds() {
return mBoundedRect.getInner();
}
public RectF getOuterBounds() {
return mBoundedRect.getOuter();
}
public int getSelectState() {
return mMovingEdges;
}
public boolean isFixedAspect() {
return mFixAspectRatio;
}
public void rotateOuter(int angle) {
mRotation = angle % 360;
mBoundedRect.setRotation(mRotation);
clearSelectState();
}
public boolean setInnerAspectRatio(float width, float height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Width and Height must be greater than zero");
}
RectF inner = mBoundedRect.getInner();
CropMath.fixAspectRatioContained(inner, width, height);
if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
return false;
}
mAspectWidth = width;
mAspectHeight = height;
mFixAspectRatio = true;
mBoundedRect.setInner(inner);
clearSelectState();
return true;
}
public void setTouchTolerance(float tolerance) {
if (tolerance <= 0) {
throw new IllegalArgumentException("Tolerance must be greater than zero");
}
mTouchTolerance = tolerance;
}
public void setMinInnerSideSize(float minSide) {
if (minSide <= 0) {
throw new IllegalArgumentException("Min dide must be greater than zero");
}
mMinSideSize = minSide;
}
public void unsetAspectRatio() {
mFixAspectRatio = false;
clearSelectState();
}
public boolean hasSelectedEdge() {
return mMovingEdges != MOVE_NONE;
}
public static boolean checkCorner(int selected) {
return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
|| selected == BOTTOM_LEFT;
}
public static boolean checkEdge(int selected) {
return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
|| selected == MOVE_BOTTOM;
}
public static boolean checkBlock(int selected) {
return selected == MOVE_BLOCK;
}
public static boolean checkValid(int selected) {
return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
|| checkCorner(selected);
}
public void clearSelectState() {
mMovingEdges = MOVE_NONE;
}
public int wouldSelectEdge(float x, float y) {
int edgeSelected = calculateSelectedEdge(x, y);
if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
return edgeSelected;
}
return MOVE_NONE;
}
public boolean selectEdge(int edge) {
if (!checkValid(edge)) {
// temporary
throw new IllegalArgumentException("bad edge selected");
// return false;
}
if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) {
// temporary
throw new IllegalArgumentException("bad corner selected");
// return false;
}
mMovingEdges = edge;
return true;
}
public boolean selectEdge(float x, float y) {
int edgeSelected = calculateSelectedEdge(x, y);
if (mFixAspectRatio) {
edgeSelected = fixEdgeToCorner(edgeSelected);
}
if (edgeSelected == MOVE_NONE) {
return false;
}
return selectEdge(edgeSelected);
}
public boolean moveCurrentSelection(float dX, float dY) {
if (mMovingEdges == MOVE_NONE) {
return false;
}
RectF crop = mBoundedRect.getInner();
float minWidthHeight = mMinSideSize;
int movingEdges = mMovingEdges;
if (movingEdges == MOVE_BLOCK) {
mBoundedRect.moveInner(dX, dY);
return true;
} else {
float dx = 0;
float dy = 0;
if ((movingEdges & MOVE_LEFT) != 0) {
dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
}
if ((movingEdges & MOVE_TOP) != 0) {
dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
}
if ((movingEdges & MOVE_RIGHT) != 0) {
dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
- crop.right;
}
if ((movingEdges & MOVE_BOTTOM) != 0) {
dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
- crop.bottom;
}
if (mFixAspectRatio) {
float[] l1 = {
crop.left, crop.bottom
};
float[] l2 = {
crop.right, crop.top
};
if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
l1[1] = crop.top;
l2[1] = crop.bottom;
}
float[] b = {
l1[0] - l2[0], l1[1] - l2[1]
};
float[] disp = {
dx, dy
};
float[] bUnit = Utils.normalize(b);
float sp = Utils.scalarProjection(disp, bUnit);
dx = sp * bUnit[0];
dy = sp * bUnit[1];
RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
mBoundedRect.fixedAspectResizeInner(newCrop);
} else {
if ((movingEdges & MOVE_LEFT) != 0) {
crop.left += dx;
}
if ((movingEdges & MOVE_TOP) != 0) {
crop.top += dy;
}
if ((movingEdges & MOVE_RIGHT) != 0) {
crop.right += dx;
}
if ((movingEdges & MOVE_BOTTOM) != 0) {
crop.bottom += dy;
}
mBoundedRect.resizeInner(crop);
}
}
return true;
}
// Helper methods
private int calculateSelectedEdge(float x, float y) {
RectF cropped = mBoundedRect.getInner();
float left = Math.abs(x - cropped.left);
float right = Math.abs(x - cropped.right);
float top = Math.abs(y - cropped.top);
float bottom = Math.abs(y - cropped.bottom);
int edgeSelected = MOVE_NONE;
// Check left or right.
if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
&& ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
edgeSelected |= MOVE_LEFT;
}
else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
&& ((y - mTouchTolerance) <= cropped.bottom)) {
edgeSelected |= MOVE_RIGHT;
}
// Check top or bottom.
if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
&& ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
edgeSelected |= MOVE_TOP;
}
else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
&& ((x - mTouchTolerance) <= cropped.right)) {
edgeSelected |= MOVE_BOTTOM;
}
return edgeSelected;
}
private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
RectF newCrop = null;
// Fix opposite corner in place and move sides
if (moving_corner == BOTTOM_RIGHT) {
newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
+ dy);
} else if (moving_corner == BOTTOM_LEFT) {
newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
+ dy);
} else if (moving_corner == TOP_LEFT) {
newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
r.right, r.bottom);
} else if (moving_corner == TOP_RIGHT) {
newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
+ r.width() + dx, r.bottom);
}
return newCrop;
}
private static int fixEdgeToCorner(int moving_edges) {
if (moving_edges == MOVE_LEFT) {
moving_edges |= MOVE_TOP;
}
if (moving_edges == MOVE_TOP) {
moving_edges |= MOVE_LEFT;
}
if (moving_edges == MOVE_RIGHT) {
moving_edges |= MOVE_BOTTOM;
}
if (moving_edges == MOVE_BOTTOM) {
moving_edges |= MOVE_RIGHT;
}
return moving_edges;
}
}

View File

@@ -0,0 +1,378 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.fengliyan.uikit.crop.app;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.fengliyan.uikit.R;
public class CropView extends View {
private static final String LOGTAG = "CropView";
private RectF mImageBounds = new RectF();
private RectF mScreenBounds = new RectF();
private RectF mScreenImageBounds = new RectF();
private RectF mScreenCropBounds = new RectF();
private Rect mShadowBounds = new Rect();
private Bitmap mBitmap;
private Paint mPaint = new Paint();
private NinePatchDrawable mShadow;
private CropObject mCropObj = null;
private Drawable mCropIndicator;
private int mIndicatorSize;
private int mRotation = 0;
private boolean mMovingBlock = false;
private Matrix mDisplayMatrix = null;
private Matrix mDisplayMatrixInverse = null;
private boolean mDirty = false;
private float mPrevX = 0;
private float mPrevY = 0;
private float mSpotX = 0;
private float mSpotY = 0;
private boolean mDoSpot = false;
private int mShadowMargin = 15;
private int mMargin = 32;
private int mOverlayShadowColor = 0xCF000000;
private int mOverlayWPShadowColor = 0x5F000000;
private int mWPMarkerColor = 0x7FFFFFFF;
private int mMinSideSize = 90;
private int mTouchTolerance = 40;
private float mDashOnLength = 20;
private float mDashOffLength = 10;
private enum Mode {
NONE, MOVE
}
private Mode mState = Mode.NONE;
public CropView(Context context) {
super(context);
setup(context);
}
public CropView(Context context, AttributeSet attrs) {
super(context, attrs);
setup(context);
}
public CropView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup(context);
}
private void setup(Context context) {
Resources rsc = context.getResources();
mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.muna_geometry_shadow);
mCropIndicator = rsc.getDrawable(R.drawable.muna_camera_crop);
mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
mOverlayWPShadowColor = (int) rsc.getColor(R.color.crop_shadow_wp_color);
mWPMarkerColor = (int) rsc.getColor(R.color.crop_wp_markers);
mDashOnLength = rsc.getDimension(R.dimen.wp_selector_dash_length);
mDashOffLength = rsc.getDimension(R.dimen.wp_selector_off_length);
}
public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) {
mBitmap = image;
if (mCropObj != null) {
RectF crop = mCropObj.getInnerBounds();
RectF containing = mCropObj.getOuterBounds();
if (crop != newCropBounds || containing != newPhotoBounds
|| mRotation != rotation) {
mRotation = rotation;
mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
clearDisplay();
}
} else {
mRotation = rotation;
mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
clearDisplay();
}
}
public RectF getCrop() {
return mCropObj.getInnerBounds();
}
public RectF getPhoto() {
return mCropObj.getOuterBounds();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
return true;
}
float[] touchPoint = {
x, y
};
mDisplayMatrixInverse.mapPoints(touchPoint);
x = touchPoint[0];
y = touchPoint[1];
switch (event.getActionMasked()) {
case (MotionEvent.ACTION_DOWN):
if (mState == Mode.NONE) {
if (!mCropObj.selectEdge(x, y)) {
mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
}
mPrevX = x;
mPrevY = y;
mState = Mode.MOVE;
}
break;
case (MotionEvent.ACTION_UP):
if (mState == Mode.MOVE) {
mCropObj.selectEdge(CropObject.MOVE_NONE);
mMovingBlock = false;
mPrevX = x;
mPrevY = y;
mState = Mode.NONE;
}
break;
case (MotionEvent.ACTION_MOVE):
if (mState == Mode.MOVE) {
float dx = x - mPrevX;
float dy = y - mPrevY;
mCropObj.moveCurrentSelection(dx, dy);
mPrevX = x;
mPrevY = y;
}
break;
default:
break;
}
invalidate();
return true;
}
private void reset() {
Log.w(LOGTAG, "crop reset called");
mState = Mode.NONE;
mCropObj = null;
mRotation = 0;
mMovingBlock = false;
clearDisplay();
}
private void clearDisplay() {
mDisplayMatrix = null;
mDisplayMatrixInverse = null;
invalidate();
}
protected void configChanged() {
mDirty = true;
}
public void applyFreeAspect() {
mCropObj.unsetAspectRatio();
invalidate();
}
public void applyOriginalAspect() {
RectF outer = mCropObj.getOuterBounds();
float w = outer.width();
float h = outer.height();
if (w > 0 && h > 0) {
applyAspect(w, h);
mCropObj.resetBoundsTo(outer, outer);
} else {
Log.w(LOGTAG, "failed to set aspect ratio original");
}
}
public void applySquareAspect() {
applyAspect(1, 1);
}
public void applyAspect(float x, float y) {
if (x <= 0 || y <= 0) {
throw new IllegalArgumentException("Bad arguments to applyAspect");
}
// If we are rotated by 90 degrees from horizontal, swap x and y
if (((mRotation < 0) ? -mRotation : mRotation) % 180 == 90) {
float tmp = x;
x = y;
y = tmp;
}
if (!mCropObj.setInnerAspectRatio(x, y)) {
Log.w(LOGTAG, "failed to set aspect ratio");
}
invalidate();
}
public void setWallpaperSpotlight(float spotlightX, float spotlightY) {
mSpotX = spotlightX;
mSpotY = spotlightY;
if (mSpotX > 0 && mSpotY > 0) {
mDoSpot = true;
}
}
public void unsetWallpaperSpotlight() {
mDoSpot = false;
}
/**
* Rotates first d bits in integer x to the left some number of times.
*/
private int bitCycleLeft(int x, int times, int d) {
int mask = (1 << d) - 1;
int mout = x & mask;
times %= d;
int hi = mout >> (d - times);
int low = (mout << times) & mask;
int ret = x & ~mask;
ret |= low;
ret |= hi;
return ret;
}
/**
* Find the selected edge or corner in screen coordinates.
*/
private int decode(int movingEdges, float rotation) {
int rot = CropMath.constrainedRotation(rotation);
switch (rot) {
case 90:
return bitCycleLeft(movingEdges, 1, 4);
case 180:
return bitCycleLeft(movingEdges, 2, 4);
case 270:
return bitCycleLeft(movingEdges, 3, 4);
default:
return movingEdges;
}
}
@Override
public void onDraw(Canvas canvas) {
if (mBitmap == null) {
return;
}
if (mDirty) {
mDirty = false;
clearDisplay();
}
mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
mScreenBounds.inset(mMargin, mMargin);
// If crop object doesn't exist, create it and update it from master
// state
if (mCropObj == null) {
reset();
mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
}
// If display matrix doesn't exist, create it and its dependencies
if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
mDisplayMatrix = new Matrix();
mDisplayMatrix.reset();
if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
mRotation)) {
Log.w(LOGTAG, "failed to get screen matrix");
mDisplayMatrix = null;
return;
}
mDisplayMatrixInverse = new Matrix();
mDisplayMatrixInverse.reset();
if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
Log.w(LOGTAG, "could not invert display matrix");
mDisplayMatrixInverse = null;
return;
}
// Scale min side and tolerance by display matrix scale factor
mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
}
mScreenImageBounds.set(mImageBounds);
// Draw background shadow
if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
mScreenImageBounds.roundOut(mShadowBounds);
mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
mShadow.setBounds(mShadowBounds);
mShadow.draw(canvas);
}
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
// Draw actual bitmap
canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
mCropObj.getInnerBounds(mScreenCropBounds);
if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
// Draw overlay shadows
Paint p = new Paint();
p.setColor(mOverlayShadowColor);
p.setStyle(Paint.Style.FILL);
CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
// Draw crop rect and markers
CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
if (!mDoSpot) {
CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
} else {
Paint wpPaint = new Paint();
wpPaint.setColor(mWPMarkerColor);
wpPaint.setStrokeWidth(3);
wpPaint.setStyle(Paint.Style.STROKE);
wpPaint.setPathEffect(new DashPathEffect(new float[]
{mDashOnLength, mDashOnLength + mDashOffLength}, 0));
p.setColor(mOverlayWPShadowColor);
CropDrawingUtils.drawWallpaperSelectionFrame(canvas, mScreenCropBounds,
mSpotX, mSpotY, wpPaint, p);
}
CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mRotation));
}
}
}

View File

@@ -0,0 +1,432 @@
package com.fengliyan.uikit.crop.app;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by neu on 16/1/12.
*/
public class Utils {
private static final String LOGTAG = "SaveImage";
public static final int ORI_ROTATE_90 = 6;
public static final int ORI_ROTATE_180 = 3;
public static final int ORI_ROTATE_270 = 8;
public static final short ORI_NORMAL = 1;
private static final String TIME_STAMP_NAME = "_yyyyMMdd_HHmmss";
public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
public static final String JPEG_MIME_TYPE = "image/jpeg";
// Math operations for 2d vectors
public static float clamp(float i, float low, float high) {
return Math.max(Math.min(i, high), low);
}
public static float vectorLength(float[] a) {
return (float) Math.hypot(a[0], a[1]);
}
public static float[] shortestVectorFromPointToLine(float[] point, float[] line) {
float x1 = line[0];
float x2 = line[2];
float y1 = line[1];
float y2 = line[3];
float xdelt = x2 - x1;
float ydelt = y2 - y1;
if (xdelt == 0 && ydelt == 0)
return null;
float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt)
/ (xdelt * xdelt + ydelt * ydelt);
float[] ret = {
(x1 + u * (x2 - x1)), (y1 + u * (y2 - y1))
};
float[] vec = {
ret[0] - point[0], ret[1] - point[1]
};
return vec;
}
public static float[] lineIntersect(float[] line1, float[] line2) {
float a0 = line1[0];
float a1 = line1[1];
float b0 = line1[2];
float b1 = line1[3];
float c0 = line2[0];
float c1 = line2[1];
float d0 = line2[2];
float d1 = line2[3];
float t0 = a0 - b0;
float t1 = a1 - b1;
float t2 = b0 - d0;
float t3 = d1 - b1;
float t4 = c0 - d0;
float t5 = c1 - d1;
float denom = t1 * t4 - t0 * t5;
if (denom == 0)
return null;
float u = (t3 * t4 + t5 * t2) / denom;
float[] intersect = {
b0 + u * t0, b1 + u * t1
};
return intersect;
}
public static float[] normalize(float[] a) {
float length = (float) Math.hypot(a[0], a[1]);
float[] b = {
a[0] / length, a[1] / length
};
return b;
}
// A onto B
public static float scalarProjection(float[] a, float[] b) {
float length = (float) Math.hypot(b[0], b[1]);
return dotProduct(a, b) / length;
}
// A . B
public static float dotProduct(float[] a, float[] b) {
return a[0] * b[0] + a[1] * b[1];
}
public static void closeSilently(Closeable c) {
if (c == null) return;
try {
c.close();
} catch (Throwable t) {
//Log.w(TAG, "close fail ", t);
}
}
public static Bitmap loadConstrainedBitmap(Uri uri, Context context, int maxSideLength,
Rect originalBounds, boolean useMin) {
if (maxSideLength <= 0 || uri == null || context == null) {
throw new IllegalArgumentException("bad argument to getScaledBitmap");
}
// Get width and height of stored bitmap
Rect storedBounds = loadBitmapBounds(context, uri);
if (originalBounds != null) {
originalBounds.set(storedBounds);
}
int w = storedBounds.width();
int h = storedBounds.height();
// If bitmap cannot be decoded, return null
if (w <= 0 || h <= 0) {
return null;
}
// Find best downsampling size
int imageSide = 0;
if (useMin) {
imageSide = Math.min(w, h);
} else {
imageSide = Math.max(w, h);
}
int sampleSize = 1;
while (imageSide > maxSideLength) {
imageSide >>>= 1;
sampleSize <<= 1;
}
// Make sure sample size is reasonable
if (sampleSize <= 0 ||
0 >= (int) (Math.min(w, h) / sampleSize)) {
return null;
}
return loadDownsampledBitmap(context, uri, sampleSize);
}
/**
* Returns the bounds of the bitmap stored at a given Url.
*/
public static Rect loadBitmapBounds(Context context, Uri uri) {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
loadBitmap(context, uri, o);
return new Rect(0, 0, o.outWidth, o.outHeight);
}
/**
* Loads a bitmap that has been downsampled using sampleSize from a given url.
*/
public static Bitmap loadDownsampledBitmap(Context context, Uri uri, int sampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inSampleSize = sampleSize;
return loadBitmap(context, uri, options);
}
/**
* Returns the bitmap from the given uri loaded using the given options.
* Returns null on failure.
*/
public static Bitmap loadBitmap(Context context, Uri uri, BitmapFactory.Options o) {
if (uri == null || context == null) {
throw new IllegalArgumentException("bad argument to loadBitmap");
}
InputStream is = null;
try {
is = context.getContentResolver().openInputStream(uri);
return BitmapFactory.decodeStream(is, null, o);
} catch (FileNotFoundException e) {
} catch (OutOfMemoryError e) {
} finally {
Utils.closeSilently(is);
}
return null;
}
/**
* Returns the rotation of image at the given URI as one of 0, 90, 180,
* 270. Defaults to 0.
*/
public static int getMetadataRotation(Context context, Uri uri) {
int orientation = getMetadataOrientation(context, uri);
switch (orientation) {
case ORI_ROTATE_90:
return 90;
case ORI_ROTATE_180:
return 180;
case ORI_ROTATE_270:
return 270;
default:
return 0;
}
}
/**
* Returns the image's orientation flag. Defaults to ORI_NORMAL if no valid
* orientation was found.
*/
public static int getMetadataOrientation(Context context, Uri uri) {
if (uri == null || context == null) {
throw new IllegalArgumentException("bad argument to getOrientation");
}
// First try to find orientation data in Gallery's ContentProvider.
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri,
new String[] { ImageColumns.ORIENTATION },
null, null, null);
if (cursor != null && cursor.moveToNext()) {
int ori = cursor.getInt(0);
switch (ori) {
case 90:
return ORI_ROTATE_90;
case 270:
return ORI_ROTATE_270;
case 180:
return ORI_ROTATE_180;
default:
return ORI_NORMAL;
}
}
} catch (SQLiteException e) {
// Do nothing
} catch (IllegalArgumentException e) {
// Do nothing
} catch (IllegalStateException e) {
// Do nothing
} finally {
Utils.closeSilently(cursor);
}
return ORI_NORMAL;
}
public static Uri makeAndInsertUri(Context context, Uri sourceUri) {
long time = System.currentTimeMillis();
String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time));
File saveDirectory = getFinalSaveDirectory(context, sourceUri);
File file = new File(saveDirectory, filename + ".JPG");
return linkNewFileToUri(context, sourceUri, file, time, false);
}
public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
File saveDirectory = getSaveDirectory(context, sourceUri);
if ((saveDirectory == null) || !saveDirectory.canWrite()) {
saveDirectory = new File(context.getExternalFilesDir(""),
DEFAULT_SAVE_DIRECTORY);
}
// Create the directory if it doesn't exist
if (!saveDirectory.exists())
saveDirectory.mkdirs();
return saveDirectory;
}
private static File getSaveDirectory(Context context, Uri sourceUri) {
File file = getLocalFileFromUri(context, sourceUri);
if (file != null) {
return file.getParentFile();
} else {
return null;
}
}
public static Uri linkNewFileToUri(Context context, Uri sourceUri,
File file, long time, boolean deleteOriginal) {
File oldSelectedFile = getLocalFileFromUri(context, sourceUri);
final ContentValues values = getContentValues(context, sourceUri, file, time);
Uri result = sourceUri;
// In the case of incoming Uri is just a local file Uri (like a cached
// file), we can't just update the Uri. We have to create a new Uri.
boolean fileUri = isFileUri(sourceUri);
if (fileUri || oldSelectedFile == null || !deleteOriginal) {
result = context.getContentResolver().insert(
Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
context.getContentResolver().update(sourceUri, values, null, null);
if (oldSelectedFile.exists()) {
oldSelectedFile.delete();
}
}
return result;
}
/**
* @param sourceUri
* @return true if the sourceUri is a local file Uri.
*/
private static boolean isFileUri(Uri sourceUri) {
String scheme = sourceUri.getScheme();
if (scheme != null && scheme.equals(ContentResolver.SCHEME_FILE)) {
return true;
}
return false;
}
private static ContentValues getContentValues(Context context, Uri sourceUri,
File file, long time) {
final ContentValues values = new ContentValues();
time /= 1000;
values.put(Images.Media.TITLE, file.getName());
values.put(Images.Media.DISPLAY_NAME, file.getName());
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.Media.DATE_TAKEN, time);
values.put(Images.Media.DATE_MODIFIED, time);
values.put(Images.Media.DATE_ADDED, time);
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, file.getAbsolutePath());
values.put(Images.Media.SIZE, file.length());
// This is a workaround to trigger the MediaProvider to re-generate the
// thumbnail.
values.put(Images.Media.MINI_THUMB_MAGIC, 0);
final String[] projection = new String[] {
ImageColumns.DATE_TAKEN,
ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
};
querySource(context, sourceUri, projection,
new ContentResolverQueryCallback() {
@Override
public void onCursorResult(Cursor cursor) {
values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
double latitude = cursor.getDouble(1);
double longitude = cursor.getDouble(2);
// issue is fixed.
if ((latitude != 0f) || (longitude != 0f)) {
values.put(Images.Media.LATITUDE, latitude);
values.put(Images.Media.LONGITUDE, longitude);
}
}
});
return values;
}
/**
* Construct a File object based on the srcUri.
* @return The file object. Return null if srcUri is invalid or not a local
* file.
*/
private static File getLocalFileFromUri(Context context, Uri srcUri) {
if (srcUri == null) {
Log.e(LOGTAG, "srcUri is null.");
return null;
}
String scheme = srcUri.getScheme();
if (scheme == null) {
Log.e(LOGTAG, "scheme is null.");
return null;
}
final File[] file = new File[1];
// sourceUri can be a file path or a content Uri, it need to be handled
// differently.
if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
if (srcUri.getAuthority().equals(MediaStore.AUTHORITY)) {
querySource(context, srcUri, new String[] {
ImageColumns.DATA
},
new ContentResolverQueryCallback() {
@Override
public void onCursorResult(Cursor cursor) {
file[0] = new File(cursor.getString(0));
}
});
}
} else if (scheme.equals(ContentResolver.SCHEME_FILE)) {
file[0] = new File(srcUri.getPath());
}
return file[0];
}
public static void querySource(Context context, Uri sourceUri, String[] projection,
ContentResolverQueryCallback callback) {
ContentResolver contentResolver = context.getContentResolver();
querySourceFromContentResolver(contentResolver, sourceUri, projection, callback);
}
private static void querySourceFromContentResolver(
ContentResolver contentResolver, Uri sourceUri, String[] projection,
ContentResolverQueryCallback callback) {
Cursor cursor = null;
try {
cursor = contentResolver.query(sourceUri, projection, null, null,
null);
if ((cursor != null) && cursor.moveToNext()) {
callback.onCursorResult(cursor);
}
} catch (Exception e) {
// Ignore error for lacking the data column from the source.
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public interface ContentResolverQueryCallback {
void onCursorResult(Cursor cursor);
}
}

View File

@@ -0,0 +1,55 @@
package com.fengliyan.uikit.dialog;
import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import com.fengliyan.uikit.R;
public class BaseDialog extends Dialog {
private static final String TAG = "BaseDialog";
private Context mContext;
public BaseDialog(Context context) {
super(context, android.R.style.Theme_Dialog);
requestWindowFeature(Window.FEATURE_NO_TITLE);
mContext = context;
getWindow().setBackgroundDrawableResource(R.drawable.muna_base_dialog_bg);
}
@Override
public void setContentView(int layoutResID) {
View view = View.inflate(mContext, layoutResID, null);
LayoutParams params = new LayoutParams((int) mContext.getResources().getDimension(R.dimen.muna_base_dialog_width), LayoutParams.MATCH_PARENT);
super.setContentView(view, params);
}
@Override
public void setContentView(View view) {
LayoutParams params = new LayoutParams((int) mContext.getResources().getDimension(R.dimen.muna_base_dialog_width), LayoutParams.MATCH_PARENT);
super.setContentView(view, params);
}
@Override
public void show() {
try {
super.show();
} catch (Exception e) {
}
}
@Override
public void dismiss() {
try {
super.dismiss();
} catch (Exception e) {
}
}
}

View File

@@ -0,0 +1,372 @@
package com.fengliyan.uikit.dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.fengliyan.uikit.R;
/**
* 统一的消息提示对话框
*/
public class BaseMessageDialog extends BaseDialog {
/**
* 一个按钮的消息对话框
*/
public static final int TYPE_ONE_BUTTON = 1;
/**
* 两个按钮的消息对话框
*/
public static final int TYPE_TWO_BUTTON = 2;
private int mType = TYPE_ONE_BUTTON;
private DialogInterface.OnClickListener mPositiveButtonListener = null;
private DialogInterface.OnClickListener mNegativeButtonListener = null;
private Button confirm;
private Button cancel;
private Button okBtn;
private CharSequence mMessage;
private CharSequence mPositive;
private CharSequence mNegative;
private CharSequence mTitle;
private View oneBtnLayout;
private View twoBtnLayout;
private View mCustomView;
private int mHighLightButton;
/**
* 构造方法
*/
public BaseMessageDialog(Context context) {
this(context, TYPE_ONE_BUTTON);
}
public BaseMessageDialog(Context context, int type) {
super(context);
this.mType = type;
init();
}
private BaseMessageDialog(Builder builder) {
super(builder.mContext);
mPositiveButtonListener = builder.mPositiveButtonListener;
mNegativeButtonListener = builder.mNegativeButtonListener;
mMessage = builder.mMessage;
mPositive = builder.mPositive;
mNegative = builder.mNegative;
mTitle = builder.mTitle;
mCustomView = builder.mCustomView;
if (mNegative == null && mNegativeButtonListener == null) {
mType = TYPE_ONE_BUTTON;
} else {
mType = TYPE_TWO_BUTTON;
}
mHighLightButton = builder.mHighLightButton;
setCancelable(builder.mCancelable);
setOnCancelListener(builder.mOnCancelListener);
init();
}
private void init() {
setContentView(R.layout.muna_base_dialog_message);
confirm = (Button) findViewById(R.id.base_message_positive_button);
cancel = (Button) findViewById(R.id.base_message_negative_button);
okBtn = (Button) findViewById(R.id.base_message_ok_button);
oneBtnLayout = findViewById(R.id.base_message_one_button_layout);
twoBtnLayout = findViewById(R.id.base_message_two_button_layout);
}
@Override
public void onStart() {
super.onStart();
FrameLayout content = (FrameLayout) findViewById(R.id.base_message_content_text);
if (mCustomView != null) {
content.addView(mCustomView);
} else if (TextUtils.isEmpty(mTitle)) {
content.addView(View.inflate(getContext(), R.layout.muna_base_dialog_message_content_no_title, null));
TextView messageText = (TextView) findViewById(R.id.base_message_message_text);
messageText.setText(mMessage);
} else {
content.addView(View.inflate(getContext(), R.layout.muna_base_dialog_message_content, null));
TextView titleText = (TextView) findViewById(R.id.base_message_title_text);
titleText.setText(mTitle);
TextView messageText = (TextView) findViewById(R.id.base_message_message_text);
messageText.setText(mMessage);
}
okBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPositiveButtonListener != null) {
mPositiveButtonListener.onClick(BaseMessageDialog.this, BUTTON_POSITIVE);
} else {
dismiss();
}
}
});
confirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPositiveButtonListener != null) {
mPositiveButtonListener.onClick(BaseMessageDialog.this, BUTTON_POSITIVE);
} else {
dismiss();
}
}
});
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mNegativeButtonListener != null) {
mNegativeButtonListener.onClick(BaseMessageDialog.this, BUTTON_NEGATIVE);
} else {
dismiss();
}
}
});
if (mType == TYPE_ONE_BUTTON) {
oneBtnLayout.setVisibility(View.VISIBLE);
twoBtnLayout.setVisibility(View.GONE);
if (!TextUtils.isEmpty(mPositive)) {
okBtn.setText(mPositive);
}
if (mHighLightButton == BUTTON_POSITIVE) {
okBtn.getPaint().setFakeBoldText(true);
}
} else {
oneBtnLayout.setVisibility(View.GONE);
twoBtnLayout.setVisibility(View.VISIBLE);
if (!TextUtils.isEmpty(mPositive)) {
confirm.setText(mPositive);
}
if (!TextUtils.isEmpty(mNegative)) {
cancel.setText(mNegative);
}
if (mHighLightButton == BUTTON_POSITIVE) {
confirm.getPaint().setFakeBoldText(true);
} else if (mHighLightButton == BUTTON_NEGATIVE) {
cancel.getPaint().setFakeBoldText(true);
}
}
}
/**
* 设置标题
*
* @param title 标题内容
*/
public BaseMessageDialog setDialogTitle(CharSequence title) {
this.mTitle = title;
return this;
}
public BaseMessageDialog setDialogTitle(int resid) {
this.mTitle = getContext().getResources().getString(resid);
return this;
}
/**
* 设置消息内容
*
* @param message 消息内容
*/
public BaseMessageDialog setMessage(CharSequence message) {
this.mMessage = message;
return this;
}
public BaseMessageDialog setMessage(int resid) {
this.mMessage = getContext().getResources().getString(resid);
return this;
}
/**
* 设置确定按钮点击事件监听函数,当只有一个按钮时,此事件生效
*
* @param l 监听函数
*/
public BaseMessageDialog setPositiveButton(DialogInterface.OnClickListener l) {
mPositiveButtonListener = l;
return this;
}
/**
* 设置取消按钮的监听事件,当只有一个按钮时,此事件无效
*
* @param l 监听函数
*/
public BaseMessageDialog setNegativeButton(DialogInterface.OnClickListener l) {
mNegativeButtonListener = l;
return this;
}
/**
* 设置确定按钮的文字,当只有一个按钮时,此文字为按钮显示的文字
*
* @param text 按钮文字
*/
public BaseMessageDialog setPositiveText(CharSequence text) {
mPositive = text;
return this;
}
/**
* 设置取消按钮的文字,当只有一个按钮时,此方法设置的文字不生效
*
* @param text 按钮文字
*/
public BaseMessageDialog setNegativeText(CharSequence text) {
mNegative = text;
return this;
}
public void setPositiveEnable(boolean enable) {
confirm.setEnabled(enable);
okBtn.setEnabled(enable);
}
public void setNegativeEnable(boolean enable) {
cancel.setEnabled(enable);
}
public static class Builder {
private Context mContext;
private CharSequence mMessage;
private CharSequence mPositive;
private CharSequence mNegative;
private CharSequence mTitle;
private DialogInterface.OnClickListener mPositiveButtonListener = null;
private DialogInterface.OnClickListener mNegativeButtonListener = null;
private DialogInterface.OnCancelListener mOnCancelListener = null;
private View mCustomView;
private boolean mCancelable = true;
private int mHighLightButton;
public Builder() {
}
public Builder(Context context) {
mContext = context;
}
/**
* 设置显示内容
*/
public Builder setMessage(CharSequence message) {
this.mMessage = message;
return this;
}
/**
* 设置右边按钮标题
*/
public Builder setPositive(CharSequence positive) {
this.mPositive = positive;
return this;
}
/**
* 设置左边按钮标题
*/
public Builder setNegative(CharSequence negative) {
this.mNegative = negative;
return this;
}
/**
* 设置整个对话框标题
*/
public Builder setTitle(CharSequence title) {
this.mTitle = title;
return this;
}
/**
* 设置右边按钮监听
*/
public Builder setPositiveButtonListener(DialogInterface.OnClickListener positiveButtonListener) {
this.mPositiveButtonListener = positiveButtonListener;
return this;
}
/**
* 设置左边操作的按钮监听
*/
public Builder setNegativeButtonListener(DialogInterface.OnClickListener negativeButtonListener) {
this.mNegativeButtonListener = negativeButtonListener;
return this;
}
/**
* 设置取消的监听
*
* @param onCancelListener 取消的监听
*/
public Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
mOnCancelListener = onCancelListener;
return this;
}
/**
* 设置自定义的view,不包括底部的按钮
*
* @param customView 自定义view
*/
public Builder setCustomView(View customView) {
mCustomView = customView;
return this;
}
public Builder setContext(Context context) {
mContext = context;
return this;
}
/**
* 设置dialog是否可取消
*
* @param cancelable true 可以
*/
public Builder setCancelable(boolean cancelable) {
mCancelable = cancelable;
return this;
}
/**
* 设置粗体的按钮,取值有{@link BaseMessageDialog#BUTTON_POSITIVE}{@link BaseMessageDialog#BUTTON_NEGATIVE}
*
* @param highLightButton 设置粗体的按钮
*/
public Builder setHighLightButton(int highLightButton) {
mHighLightButton = highLightButton;
return this;
}
public BaseMessageDialog build() {
return new BaseMessageDialog(this);
}
}
}

View File

@@ -0,0 +1,120 @@
package com.fengliyan.uikit.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
import com.fengliyan.uikit.R;
public class BottomGiftDialog extends Dialog {
private Context mContext;
private ViewPager mViewPager;
private LinearLayout mViewGroup;
private TextView mCaiBiText;
private View mCaiBiLayout;
private Button mSendButton;
private GiftListBean mGiftListBean;
private GiftPagesManager mGiftManager;
private OnGiftItemClickedListener mGiftListener;
private TextView mTvGiveGift;
public interface OnGiftItemClickedListener {
void onGiftClicked(DialogGiftBean bean);
void onWalletClicked();
}
public void setOnGiftItemClickedListener(OnGiftItemClickedListener listener) {
mGiftListener = listener;
}
public BottomGiftDialog(@NonNull Context context) {
super(context);
mContext = context;
}
public BottomGiftDialog(@NonNull Context context, int themeResId) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
protected BottomGiftDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
public void setGiftListBean(GiftListBean bean) {
mGiftListBean = bean;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_gift);
initView();
Window window = this.getWindow();
window.setGravity(Gravity.BOTTOM);
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
}
private DialogGiftBean mDialogGiftBean;
private void initView() {
mViewPager = findViewById(R.id.gift_pager);
mViewGroup = findViewById(R.id.gift_view_group);
mCaiBiText = findViewById(R.id.gift_shell_number);
mCaiBiLayout = findViewById(R.id.gift_shell_layout);
mSendButton = findViewById(R.id.gift_button);
mTvGiveGift = findViewById(R.id.tv_give_gift);
mTvGiveGift.setEnabled(false);
mCaiBiText.setText(mGiftListBean.getCoin() + "");
mGiftManager = new GiftPagesManager(mViewPager, mViewGroup, mContext);
mGiftManager.addGiftBean(mGiftListBean.getGift_list());
mGiftManager.manage();
mGiftManager.setOnGiftItemClickedListener(new GiftPagesManager.OnGiftItemClickedListener() {
@Override
public void onGiftClicked(DialogGiftBean bean) {
mTvGiveGift.setEnabled(true);
mDialogGiftBean = bean;
}
});
mCaiBiLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != mGiftListener) {
mGiftListener.onWalletClicked();
}
}
});
mTvGiveGift.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != mGiftListener) {
mGiftListener.onGiftClicked(mDialogGiftBean);
}
}
});
}
public void setCoin(String coin) {
mCaiBiText.setText(coin);
}
}

View File

@@ -0,0 +1,117 @@
package com.fengliyan.uikit.dialog;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class BottomSelectiveDialog extends Dialog {
private Context mContext;
private LinearLayout mSelectiveLayout;
private TextView mCancelButton;
private List<TextView> mButtonList = new ArrayList<>();
private List<OnButtonSelectListener> mListenerList = new ArrayList<>();
public interface OnButtonSelectListener {
void onClicked(View view, int index);
}
public TextView addSelectButton(String buttonName, OnButtonSelectListener listener) {
TextView textView = new TextView(mContext);
LinearLayout.LayoutParams params = new LinearLayout
.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 128);
textView.setLayoutParams(params);
textView.setText(buttonName);
textView.setTextSize(17);
textView.setTextColor(mContext.getResources().getColor(R.color.editTextColor));
textView.setGravity(Gravity.CENTER);
textView.setBackgroundColor(Color.WHITE);
final int count = mButtonList.size();
final OnButtonSelectListener tmplistener = listener;
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != tmplistener) {
tmplistener.onClicked(view, count);
}
dismiss();
}
});
mButtonList.add(textView);
return textView;
}
public void cleanButtons() {
mButtonList.clear();
}
public BottomSelectiveDialog(@NonNull Context context) {
super(context);
mContext = context;
}
public BottomSelectiveDialog(@NonNull Context context, int themeResId) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
protected BottomSelectiveDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_selector);
initView();
Iterator<TextView> iterator = mButtonList.iterator();
while (iterator.hasNext()) {
TextView textView = iterator.next();
mSelectiveLayout.addView(textView);
View line = new View(mContext);
LinearLayout.LayoutParams params = new LinearLayout
.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2);
line.setBackgroundResource(R.color.bottomCuttingLine);
line.setLayoutParams(params);
mSelectiveLayout.addView(line);
}
Window window = this.getWindow();
window.setGravity(Gravity.BOTTOM);
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
}
private void initView() {
mSelectiveLayout = findViewById(R.id.selector_layout);
mCancelButton = findViewById(R.id.selector_cancel);
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dismiss();
}
});
}
}

View File

@@ -0,0 +1,113 @@
package com.fengliyan.uikit.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fengliyan.uikit.R;
public class BottomShareDialog extends Dialog implements View.OnClickListener {
private Context mContext;
private View mErView;
private View mWeixinView;
private View mPengView;
private View mQQView;
private View mKongView;
private View mWeiboView;
private View mLinkView;
private OnButtonSelectListener mListener;
@Override
public void onClick(View view) {
if(view == mErView){
if(null != mListener){
mListener.onClicked(view, 0);
}
}else if(view == mWeixinView){
if(null != mListener){
mListener.onClicked(view, 1);
}
}else if(view == mPengView){
if(null != mListener){
mListener.onClicked(view, 2);
}
}else if(view == mQQView){
if(null != mListener){
mListener.onClicked(view, 3);
}
}else if(view == mKongView){
if(null != mListener){
mListener.onClicked(view, 4);
}
}else if(view == mWeiboView){
if(null != mListener){
mListener.onClicked(view, 5);
}
}else if(view == mLinkView){
if(null != mListener){
mListener.onClicked(view, 6);
}
}
}
public interface OnButtonSelectListener{
void onClicked(View view, int index);
}
public void setOnButtonSelectListener(OnButtonSelectListener listener){
mListener = listener;
}
public BottomShareDialog(@NonNull Context context) {
super(context);
mContext = context;
}
public BottomShareDialog(@NonNull Context context, int themeResId) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
protected BottomShareDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_share);
initView();
Window window = this.getWindow();
window.setGravity(Gravity.BOTTOM);
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
}
private void initView(){
mErView = findViewById(R.id.share_er);
mWeixinView = findViewById(R.id.share_weixin);
mPengView = findViewById(R.id.share_peng);
mQQView = findViewById(R.id.share_qq);
mKongView = findViewById(R.id.share_kong);
mWeiboView = findViewById(R.id.share_weibo);
mLinkView = findViewById(R.id.share_link);
mErView.setOnClickListener(this);
mWeixinView.setOnClickListener(this);
mPengView.setOnClickListener(this);
mQQView.setOnClickListener(this);
mKongView.setOnClickListener(this);
mWeiboView.setOnClickListener(this);
mLinkView.setOnClickListener(this);
}
}

View File

@@ -0,0 +1,99 @@
package com.fengliyan.uikit.dialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fengliyan.uikit.R;
import java.io.Serializable;
public class BottomVideoDialog extends Dialog implements View.OnClickListener, Serializable {
private Context mContext;
private ImageView mVideoView;
private ImageView mCancelButton;
private TextView mVideoTag;
private OnButtonClickedListener mListener;
@Override
public void onClick(View view) {
if(mVideoView == view){
if(null != mListener){
mListener.onVideoClicked(view);
}
}else if(mCancelButton == view){
if(null != mListener){
mListener.onCancelClicked(view);
}
}
}
public interface OnButtonClickedListener{
void onVideoClicked(View view);
void onCancelClicked(View view);
}
public void setOnButtonClickedListener(OnButtonClickedListener listener){
mListener = listener;
}
public BottomVideoDialog(@NonNull Context context) {
super(context);
mContext = context;
}
public BottomVideoDialog(@NonNull Context context, int themeResId) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
protected BottomVideoDialog(@NonNull Context context, boolean cancelable, @Nullable DialogInterface.OnCancelListener cancelListener) {
super(context, R.style.SelectiveDialog);
mContext = context;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_bottom_video);
initView();
Window window = this.getWindow();
window.setGravity(Gravity.BOTTOM);
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
}
private void initView(){
mVideoView = findViewById(R.id.bottom_video_logo);
mVideoTag = findViewById(R.id.bottom_video_text);
mCancelButton = findViewById(R.id.bottom_video_cancel);
mVideoView.setOnClickListener(this);
mCancelButton.setOnClickListener(this);
}
public void enableVideo(){
mVideoView.setImageResource(R.drawable.ic_home_live2);
mVideoTag.setText("关闭接单");
mVideoTag.setTextColor(Color.parseColor("#FF3352"));
}
public void disableVideo(){
mVideoView.setImageResource(R.drawable.ic_home_live1);
mVideoTag.setText("开始接单");
mVideoTag.setTextColor(mContext.getResources().getColor(R.color.bottomTabTextColor));
}
}

View File

@@ -0,0 +1,58 @@
package com.fengliyan.uikit.dialog;
public class DialogGiftBean {
private int id;
private String effect;
private String name;
private int price;
private String img;
private int isSelect; //1选中
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public int getIsSelect() {
return isSelect;
}
public void setIsSelect(int isSelect) {
this.isSelect = isSelect;
}
public String getEffect() {
return effect;
}
public void setEffect(String effect) {
this.effect = effect;
}
}

View File

@@ -0,0 +1,25 @@
package com.fengliyan.uikit.dialog;
import java.util.ArrayList;
import java.util.List;
public class GiftListBean {
private List<DialogGiftBean> gift_list = new ArrayList<>();
private int coin;
public List<DialogGiftBean> getGift_list() {
return gift_list;
}
public void setGift_list(List<DialogGiftBean> gift_list) {
this.gift_list = gift_list;
}
public int getCoin() {
return coin;
}
public void setCoin(int coin) {
this.coin = coin;
}
}

View File

@@ -0,0 +1,271 @@
package com.fengliyan.uikit.dialog;
import android.content.Context;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class GiftPagesManager {
private ViewPager mAdvPager = null;
private ViewGroup mGroup = null;
private List<View> mGiftView = new ArrayList<>();
private Context mContext = null;
private AtomicInteger mWhat = new AtomicInteger(0);
private boolean isContinue = true;
private List<DialogGiftBean> mTotalGiftBean = new ArrayList<>();
private ImageView[] mImageViews = null;
private List<DialogGiftBean>[] mPageGiftBean = null;
private OnGiftItemClickedListener mGiftListener;
private List<GiftRecycleAdapter> mAdapters;
public interface OnGiftItemClickedListener{
void onGiftClicked(DialogGiftBean bean);
}
public void setOnGiftItemClickedListener(OnGiftItemClickedListener listener){
mGiftListener = listener;
}
public GiftPagesManager(ViewPager advPager, ViewGroup group, Context context){
this.mAdvPager = advPager;
this.mContext = context;
this.mGroup = group;
}
public void addGiftBean(List<DialogGiftBean> giftBeans){
mTotalGiftBean.addAll(giftBeans);
}
private void makeGiftView(){
if(0 == mTotalGiftBean.size()){
return;
}
Iterator<DialogGiftBean> iterator = mTotalGiftBean.iterator();
int count = 0;
int groupId = 0;
mPageGiftBean = new List[mTotalGiftBean.size() % 10 == 0 ? mTotalGiftBean.size() / 10 : mTotalGiftBean.size() / 10 + 1];
mAdapters = new ArrayList<>();
GiftRecycleAdapter adapter = new GiftRecycleAdapter(mContext);
mAdapters.add(adapter);
initView(adapter, groupId);
while(iterator.hasNext()){
DialogGiftBean bean = iterator.next();
if(count >= 10){
count = 0;
groupId ++;
adapter = new GiftRecycleAdapter(mContext);
mAdapters.add(adapter);
initView(adapter, groupId);
}
mPageGiftBean[groupId].add(bean);
adapter.notifyDataSetChanged();
count ++;
}
}
private void initView(final GiftRecycleAdapter adapter, final int groupId){
View giftView = View.inflate(mContext, R.layout.item_gift_board, null);
mGiftView.add(giftView);
RecyclerView board = giftView.findViewById(R.id.gift_recycler_view);
mPageGiftBean[groupId] = new ArrayList<>();
adapter.setGiftList(mPageGiftBean[groupId]);
adapter.setOnGiftClickListener(new GiftRecycleAdapter.OnGiftClickListener() {
@Override
public void onClicked(int position) {
if(null != mGiftListener){
mGiftListener.onGiftClicked(mPageGiftBean[groupId].get(position));
int size = mTotalGiftBean.size() % 10 == 0 ? mTotalGiftBean.size() / 10 : mTotalGiftBean.size() / 10 + 1;
for (int i = 0; i < size; i++) {
mAdapters.get(i).setSelect(-1);
mAdapters.get(i).notifyDataSetChanged();
}
adapter.setSelect(position);
adapter.notifyDataSetChanged();
}
}
});
board.setAdapter(adapter);
// board.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL));
board.setLayoutManager(new GridLayoutManager(mContext, 5));
}
public void manage(){
makeGiftView();
// int size = mTotalGiftBean.size() % 10 == 0 ? mTotalGiftBean.size() / 10 : mTotalGiftBean.size() / 10 + 1;
// mImageViews = new ImageView[size];
// for (int i = 0; i < size; i++) {
// ImageView imageView = new ImageView(mContext);
// LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(10, 10);
// params.setMargins(12, 0, 0, 0);
// imageView.setLayoutParams(params);
// imageView.setPadding(UiUtils.dip2px(mContext, 22), 12, UiUtils.dip2px(mContext, 22), 12);
// mImageViews[i] = imageView;
// if (i == 0) {
// mImageViews[i]
// .setBackgroundResource(R.drawable.shape_banner_index_focus);
// } else {
// mImageViews[i]
// .setBackgroundResource(R.drawable.shape_banner_index_inactived);
// }
//
// mGroup.addView(mImageViews[i]);
// }
mAdvPager.setAdapter(new AdvAdapter(mGiftView));
mAdvPager.setOnPageChangeListener(new GuidePageChangeListener());
// mAdvPager.setOnTouchListener(new View.OnTouchListener() {
//
// @Override
// public boolean onTouch(View v, MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// case MotionEvent.ACTION_MOVE:
// isContinue = false;
// break;
// case MotionEvent.ACTION_UP:
// isContinue = true;
//
// break;
// default:
// isContinue = true;
// break;
// }
// return false;
// }
// });
//
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// while (true) {
// if (isContinue) {
// viewHandler.sendEmptyMessage(mWhat.get());
// whatOption();
// }
// }
// }
//
// }).start();
}
// private void whatOption() {
// mWhat.incrementAndGet();
// if (mWhat.get() > mImageViews.length - 1) {
// mWhat.getAndAdd(-4);
// }
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
//
// }
// }
//
// private final Handler viewHandler = new Handler() {
//
// @Override
// public void handleMessage(Message msg) {
// mAdvPager.setCurrentItem(msg.what);
// super.handleMessage(msg);
// }
//
// };
private final class GuidePageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int arg0) {
// mWhat.getAndSet(arg0);
// for (int i = 0; i < mImageViews.length; i++) {
// mImageViews[arg0]
// .setBackgroundResource(R.drawable.shape_banner_index_focus);
// if (arg0 != i) {
// mImageViews[i]
// .setBackgroundResource(R.drawable.shape_banner_index_inactived);
// }
// }
}
}
private final class AdvAdapter extends PagerAdapter {
private List<View> views = null;
public AdvAdapter(List<View> views) {
this.views = views;
}
@Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView(views.get(arg1));
}
@Override
public void finishUpdate(View arg0) {
}
@Override
public int getCount() {
return views.size();
}
@Override
public Object instantiateItem(View arg0, int arg1) {
((ViewPager) arg0).addView(views.get(arg1), 0);
return views.get(arg1);
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void startUpdate(View arg0) {
}
}
}

View File

@@ -0,0 +1,96 @@
package com.fengliyan.uikit.dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
import java.util.List;
public class GiftRecycleAdapter extends RecyclerView.Adapter {
// public final static String IMAGE_URL = "http://xqasset.whnuanbeinikj.cn/";
public final static String IMAGE_URL = "http://xqasset.whnuanbeinikj.cn/";
private List<DialogGiftBean> mGiftBeanList = new ArrayList<>();
private Context mContext;
private OnGiftClickListener mListener;
private int selectPosition = -1;
public interface OnGiftClickListener{
void onClicked(int position);
}
public void setOnGiftClickListener(OnGiftClickListener listener){
mListener = listener;
}
public GiftRecycleAdapter(Context context){
mContext = context;
}
public void setGiftList(List<DialogGiftBean> giftBeans){
mGiftBeanList = giftBeans;
}
public void setSelect(int position) {
this.selectPosition = position;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_gift, null);
return new Holder(mContext, view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
DialogGiftBean giftBean = mGiftBeanList.get(position);
Holder dataHolder = (Holder) holder;
dataHolder.mGiftLogo.setImageURI(IMAGE_URL + giftBean.getImg());
dataHolder.mName.setText(giftBean.getName());
dataHolder.mCost.setText(giftBean.getPrice() + "金币");
if (selectPosition == position) {
// dataHolder.llItem.setBackgroundColor(Color.parseColor("#ff00ff"));
dataHolder.llItem.setBackgroundResource(R.drawable.shape_gift_orange_bg);
}else {
dataHolder.llItem.setBackgroundResource(0);
}
dataHolder.mThisView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onClicked(position);
}
});
}
@Override
public int getItemCount() {
return mGiftBeanList.size();
}
public class Holder extends ViewHolder {
SimpleDraweeView mGiftLogo;
TextView mName;
TextView mCost;
View mThisView;
LinearLayout llItem;
public Holder(Context context, View itemView){
super(context, itemView);
mGiftLogo = itemView.findViewById(R.id.gift_photo);
mName = itemView.findViewById(R.id.gift_name);
mCost = itemView.findViewById(R.id.gift_cost);
llItem = itemView.findViewById(R.id.ll_item);
mThisView = itemView;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.fengliyan.uikit.dialog;
import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowManager;
import android.widget.LinearLayout;
/**
* 先判断是否设定了mMaxHeight如果设定了mMaxHeight则直接使用mMaxHeight的值
* 如果没有设定mMaxHeight则判断是否设定了mMaxRatio如果设定了mMaxRatio的值
* 则使用此值与屏幕高度的乘积作为最高高度
*
* @author Rick.Wang
*/
public class MaxHeightView extends LinearLayout {
private static final float DEFAULT_MAX_RATIO = 0.9f;
private static final float DEFAULT_MAX_HEIGHT = -1f;
private Context mContext;
private float mMaxHeight = DEFAULT_MAX_HEIGHT;//优先级高
private float mMaxRatio = DEFAULT_MAX_RATIO;//优先级低
public MaxHeightView(Context context) {
this(context, null);
}
public MaxHeightView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaxHeightView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
mMaxHeight = mMaxRatio * (float) getScreenHeight(mContext);
}
public void setMaxHeight(float maxHeight) {
mMaxHeight = maxHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
heightSize = heightSize <= mMaxHeight ? heightSize : (int) mMaxHeight;
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = heightSize <= mMaxHeight ? heightSize : (int) mMaxHeight;
}
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = heightSize <= mMaxHeight ? heightSize : (int) mMaxHeight;
}
int maxHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
super.onMeasure(widthMeasureSpec, maxHeightMeasureSpec);
}
/**
* 获取屏幕高度
*
* @param context
*/
public static int getScreenHeight(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return wm.getDefaultDisplay().getHeight();
}
}

View File

@@ -0,0 +1,232 @@
package com.fengliyan.uikit.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.Display;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.fengliyan.uikit.R;
public class PrivateDialog {
private static PrivateDialog instace;
public PrivateDialog() {
}
public static PrivateDialog getInstace() {
if (instace == null) {
synchronized (PrivateDialog.class) {
if (instace == null) {
instace = new PrivateDialog();
}
}
}
return instace;
}
private String title = "用户协议和隐私协议提示";
private String message;
private String sure;
private String cancle;
private Dialog tipDialog;
private float clickTiem = 0;
/**
* desc: 提示隐私协议框
*/
public void showConnectDialog(Context mContext) {
dismiss();
if ((SystemClock.elapsedRealtime() - clickTiem) < 500) {
return;
}
tipDialog = new AlertDialog.Builder(mContext).create();
tipDialog.setCanceledOnTouchOutside(false);
View view = LayoutInflater.from(mContext).inflate(R.layout.activity_privates_dialog, null, false);
TextView tvTitle = view.findViewById(R.id.tv_sava_dialog_title);
TextView dialogTxt = view.findViewById(R.id.tv_sava_dialog_message);
TextView cancle = view.findViewById(R.id.tv_sava_dialog_cancel);
Button sure = view.findViewById(R.id.tv_sava_dialog_confirg);
String str = dialogTxt.getText().toString();
SpannableStringBuilder tvProtocol = new SpannableStringBuilder(str);
int start = str.indexOf("《用户协议》");
int start1 = str.indexOf("《隐私政策》");
tvProtocol.setSpan(new CliclSpan(mContext,1),start,start+6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tvProtocol.setSpan(new CliclSpan(mContext,2),start1,start1+6,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
dialogTxt.setText(tvProtocol);
dialogTxt.setHighlightColor(ContextCompat.getColor(dialogTxt.getContext(),android.R.color.transparent));
dialogTxt.setMovementMethod(LinkMovementMethod.getInstance());
if (!this.title.isEmpty()) {
tvTitle.setText(this.title);
}
if (!this.message.isEmpty()) {
// tvMsg.setText(this.message);
}
if (!this.cancle.isEmpty()) {
cancle.setText(this.cancle);
}
if (!this.sure.isEmpty()) {
sure.setText(this.sure);
}
cancle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickTiem = SystemClock.elapsedRealtime();
tipDialog.dismiss();
if (listener != null) {
listener.cancleClick();
}
}
});
sure.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tipDialog.dismiss();
if (listener != null) {
listener.sureClick();
}
}
});
tipDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH)
{
return true;
}
else
{
return false; //默认返回 false
}
}
});
tipDialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
tipDialog.show();
tipDialog.setCancelable(false);
tipDialog.getWindow().setContentView(view);
//tipDialog.getWindow().setWindowAnimations(R.style.DialogBottom); // 添加动画
WindowManager windowManager = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
WindowManager.LayoutParams params = tipDialog.getWindow().getAttributes();
params.width = (int) (display.getWidth() * 0.8);
tipDialog.getWindow().setAttributes(params);
}
public void dismiss() {
if (tipDialog != null) {
if (tipDialog.isShowing()) {
tipDialog.dismiss();
}
tipDialog = null;
}
}
public interface OnTipItemClickListener {
void cancleClick();
void sureClick();
void userClick();
void termsClick();
}
private OnTipItemClickListener listener;
public PrivateDialog setOnTipItemClickListener(OnTipItemClickListener listener) {
this.listener = listener;
return this;
}
public PrivateDialog title(String title) {
this.title = title;
return this;
}
public PrivateDialog message(String message) {
this.message = message;
return this;
}
public PrivateDialog sure(String sure) {
this.sure = sure;
return this;
}
public PrivateDialog cancle(String cancle) {
this.cancle = cancle;
return this;
}
public void create(Context mContext) {
showConnectDialog(mContext);
}
class CliclSpan extends ClickableSpan {
Context mContext;
Integer mNum;
public CliclSpan() {
super();
}
public CliclSpan(Context context,Integer num) {
mContext = context;
mNum = num;
}
@Override
public void onClick(@NonNull View widget) {
if (listener != null) {
if(mNum == 1){
listener.userClick();
}else {
listener.termsClick();
}
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
ds.setColor(ContextCompat.getColor(mContext,R.color.mainColors));
ds.setUnderlineText(false);
}
}
}

View File

@@ -0,0 +1,228 @@
package com.fengliyan.uikit.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.Display;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.fengliyan.uikit.R;
public class PrivateDialogs {
private static PrivateDialogs instace;
public PrivateDialogs() {
}
public static PrivateDialogs getInstace() {
if (instace == null) {
synchronized (PrivateDialogs.class) {
if (instace == null) {
instace = new PrivateDialogs();
}
}
}
return instace;
}
private String title = "温馨提示";
private String message;
private String sure;
private String cancle;
private Dialog tipDialog;
private float clickTiem = 0;
/**
* desc: 提示隐私协议框
*/
public void showConnectDialog(Context mContext) {
dismiss();
if ((SystemClock.elapsedRealtime() - clickTiem) < 500) {
return;
}
tipDialog = new AlertDialog.Builder(mContext).create();
tipDialog.setCanceledOnTouchOutside(false);
View view = LayoutInflater.from(mContext).inflate(R.layout.activity_privates_dialogs, null, false);
TextView tvTitle = view.findViewById(R.id.tv_sava_dialog_title);
TextView dialogTxt = view.findViewById(R.id.tv_sava_dialog_message);
Button cancle = view.findViewById(R.id.tv_sava_dialog_cancel);
Button sure = view.findViewById(R.id.tv_sava_dialog_confirg);
SpannableStringBuilder tvProtocol = new SpannableStringBuilder(dialogTxt.getText().toString());
tvProtocol.setSpan(new PrivateDialogs.CliclSpan(mContext,1),3,11, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tvProtocol.setSpan(new PrivateDialogs.CliclSpan(mContext,2),12,20,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
dialogTxt.setText(tvProtocol);
dialogTxt.setHighlightColor(ContextCompat.getColor(dialogTxt.getContext(),android.R.color.transparent));
dialogTxt.setMovementMethod(LinkMovementMethod.getInstance());
if (!this.title.isEmpty()) {
tvTitle.setText(this.title);
}
if (!this.message.isEmpty()) {
// tvMsg.setText(this.message);
}
if (!this.cancle.isEmpty()) {
cancle.setText(this.cancle);
}
if (!this.sure.isEmpty()) {
sure.setText(this.sure);
}
cancle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickTiem = SystemClock.elapsedRealtime();
tipDialog.dismiss();
if (listener != null) {
listener.cancleClick();
}
}
});
sure.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tipDialog.dismiss();
if (listener != null) {
listener.sureClick();
}
}
});
tipDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH)
{
return true;
}
else
{
return false; //默认返回 false
}
}
});
tipDialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
tipDialog.show();
tipDialog.setCancelable(false);
tipDialog.getWindow().setContentView(view);
//tipDialog.getWindow().setWindowAnimations(R.style.DialogBottom); // 添加动画
WindowManager windowManager = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
WindowManager.LayoutParams params = tipDialog.getWindow().getAttributes();
params.width = (int) (display.getWidth() * 0.8);
tipDialog.getWindow().setAttributes(params);
}
public void dismiss() {
if (tipDialog != null) {
if (tipDialog.isShowing()) {
tipDialog.dismiss();
}
tipDialog = null;
}
}
public interface OnTipItemClickListener {
void cancleClick();
void sureClick();
void userClick();
void termsClick();
}
private PrivateDialogs.OnTipItemClickListener listener;
public PrivateDialogs setOnTipItemClickListener(PrivateDialogs.OnTipItemClickListener listener) {
this.listener = listener;
return this;
}
public PrivateDialogs title(String title) {
this.title = title;
return this;
}
public PrivateDialogs message(String message) {
this.message = message;
return this;
}
public PrivateDialogs sure(String sure) {
this.sure = sure;
return this;
}
public PrivateDialogs cancle(String cancle) {
this.cancle = cancle;
return this;
}
public void create(Context mContext) {
showConnectDialog(mContext);
}
class CliclSpan extends ClickableSpan {
Context mContext;
Integer mNum;
public CliclSpan() {
super();
}
public CliclSpan(Context context,Integer num) {
mContext = context;
mNum = num;
}
@Override
public void onClick(@NonNull View widget) {
if (listener != null) {
if(mNum == 1){
listener.userClick();
}else {
listener.termsClick();
}
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
ds.setColor(ContextCompat.getColor(mContext,R.color.mainColors));
ds.setUnderlineText(false);
}
}
}

View File

@@ -0,0 +1,269 @@
package com.fengliyan.uikit.dialog;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.util.Linkify;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.widget.Checkable;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RatingBar;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
public class ViewHolder extends RecyclerView.ViewHolder{
private SparseArray<View> mViews;
private View mConvertView;
private Context mContext;
public ViewHolder(Context context, View itemView)
{
super(itemView);
mContext = context;
mConvertView = itemView;
mViews = new SparseArray<View>();
}
public static ViewHolder createViewHolder(Context context, View itemView)
{
ViewHolder holder = new ViewHolder(context, itemView);
return holder;
}
public static ViewHolder createViewHolder(Context context,
ViewGroup parent, int layoutId)
{
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
ViewHolder holder = new ViewHolder(context, itemView);
return holder;
}
/**
* 通过viewId获取控件
*
* @param viewId
* @return
*/
public <T extends View> T getView(int viewId)
{
View view = mViews.get(viewId);
if (view == null)
{
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public View getConvertView()
{
return mConvertView;
}
/****以下为辅助方法*****/
/**
* 设置TextView的值
*
* @param viewId
* @param text
* @return
*/
public ViewHolder setText(int viewId, String text)
{
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
public ViewHolder setImageResource(int viewId, int resId)
{
ImageView view = getView(viewId);
view.setImageResource(resId);
return this;
}
public ViewHolder setImageBitmap(int viewId, Bitmap bitmap)
{
ImageView view = getView(viewId);
view.setImageBitmap(bitmap);
return this;
}
public ViewHolder setImageDrawable(int viewId, Drawable drawable)
{
ImageView view = getView(viewId);
view.setImageDrawable(drawable);
return this;
}
public ViewHolder setBackgroundColor(int viewId, int color)
{
View view = getView(viewId);
view.setBackgroundColor(color);
return this;
}
public ViewHolder setBackgroundRes(int viewId, int backgroundRes)
{
View view = getView(viewId);
view.setBackgroundResource(backgroundRes);
return this;
}
public ViewHolder setTextColor(int viewId, int textColor)
{
TextView view = getView(viewId);
view.setTextColor(textColor);
return this;
}
public ViewHolder setTextColorRes(int viewId, int textColorRes)
{
TextView view = getView(viewId);
view.setTextColor(mContext.getResources().getColor(textColorRes));
return this;
}
@SuppressLint("NewApi")
public ViewHolder setAlpha(int viewId, float value)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
getView(viewId).setAlpha(value);
} else
{
// Pre-honeycomb hack to set Alpha value
AlphaAnimation alpha = new AlphaAnimation(value, value);
alpha.setDuration(0);
alpha.setFillAfter(true);
getView(viewId).startAnimation(alpha);
}
return this;
}
public ViewHolder setVisible(int viewId, boolean visible)
{
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.GONE);
return this;
}
public ViewHolder linkify(int viewId)
{
TextView view = getView(viewId);
Linkify.addLinks(view, Linkify.ALL);
return this;
}
public ViewHolder setTypeface(Typeface typeface, int... viewIds)
{
for (int viewId : viewIds)
{
TextView view = getView(viewId);
view.setTypeface(typeface);
view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
}
return this;
}
public ViewHolder setProgress(int viewId, int progress)
{
ProgressBar view = getView(viewId);
view.setProgress(progress);
return this;
}
public ViewHolder setProgress(int viewId, int progress, int max)
{
ProgressBar view = getView(viewId);
view.setMax(max);
view.setProgress(progress);
return this;
}
public ViewHolder setMax(int viewId, int max)
{
ProgressBar view = getView(viewId);
view.setMax(max);
return this;
}
public ViewHolder setRating(int viewId, float rating)
{
RatingBar view = getView(viewId);
view.setRating(rating);
return this;
}
public ViewHolder setRating(int viewId, float rating, int max)
{
RatingBar view = getView(viewId);
view.setMax(max);
view.setRating(rating);
return this;
}
public ViewHolder setTag(int viewId, Object tag)
{
View view = getView(viewId);
view.setTag(tag);
return this;
}
public ViewHolder setTag(int viewId, int key, Object tag)
{
View view = getView(viewId);
view.setTag(key, tag);
return this;
}
public ViewHolder setChecked(int viewId, boolean checked)
{
Checkable view = (Checkable) getView(viewId);
view.setChecked(checked);
return this;
}
/**
* 关于事件的
*/
public ViewHolder setOnClickListener(int viewId,
View.OnClickListener listener)
{
View view = getView(viewId);
view.setOnClickListener(listener);
return this;
}
public ViewHolder setOnTouchListener(int viewId,
View.OnTouchListener listener)
{
View view = getView(viewId);
view.setOnTouchListener(listener);
return this;
}
public ViewHolder setOnLongClickListener(int viewId,
View.OnLongClickListener listener)
{
View view = getView(viewId);
view.setOnLongClickListener(listener);
return this;
}
}

View File

@@ -0,0 +1,295 @@
package com.fengliyan.uikit.editor;
import android.content.Context;
import android.text.Editable;
import android.text.InputFilter;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
/**
* Created by ChenQihong on 2016/12/15.
*/
public class AutoDivisionEditText extends EditText {
private char divideCharacter = ' ';
private int[] formatPattern = {4};
private int maxLength = 24;
private int[] divisionPattern;
private boolean isMoneyType = false;
private View.OnFocusChangeListener extFocusListener = null;
private boolean divisionEnable = true;
public AutoDivisionEditText(Context context) {
super(context);
init();
}
public AutoDivisionEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AutoDivisionEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// addTextChangedListener(mInputWatcher);
// setInputType(InputType.TYPE_CLASS_PHONE);
Divider u = new Divider();
addTextChangedListener(u);
setFilters(new InputFilter[]{u});
divisionPattern = getDivisionPatter();
final View.OnFocusChangeListener listener = getOnFocusChangeListener();
super.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (listener != null) {
listener.onFocusChange(v, hasFocus);
}
if (extFocusListener != null) {
extFocusListener.onFocusChange(v, hasFocus);
}
}
});
}
public View.OnFocusChangeListener getExtFocusListener() {
return extFocusListener;
}
//
//去掉拦截,放开某些自定义系统的调用
// @Override
// public void setOnFocusChangeListener(OnFocusChangeListener extFocusListener) {
// throw new RuntimeException("Don't call setOnFocusChangeListener on ZlEditText. "
// + "You probably want setExtFocusListener instead");
// }
/**
* 设置监听函数
*
* @param extFocusListener 监听函数
*/
public void setExtFocusListener(View.OnFocusChangeListener extFocusListener) {
this.extFocusListener = extFocusListener;
}
public boolean isDivisionEnable() {
return divisionEnable;
}
/**
* 设置是否允许分割
*
* @param divisionEnable true 自动分隔
*/
public void setDivisionEnable(boolean divisionEnable) {
this.divisionEnable = divisionEnable;
}
public boolean isMoneyType() {
return isMoneyType;
}
/**
* 设置输入类型为金额
*
* @param isMoneyType true 金额类型
*/
public void setMoneyType(boolean isMoneyType) {
this.isMoneyType = isMoneyType;
}
/**
* 字符格式化模式,例如设置为{3,4,4}字符将要显示成138 3800 3800的形式
*
* @param formatPattern 分隔模式
*/
public void setFormatPattern(int maxLength, int[] formatPattern) {
divisionEnable = true;
this.maxLength = maxLength;
this.formatPattern = formatPattern;
divisionPattern = getDivisionPatter();
}
/**
* 获取分隔字符
*/
public char getDividerCharacter() {
return divideCharacter;
}
/**
* 获取允许输入的最大长度
*/
public int getMaxLength() {
return maxLength;
}
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
/**
* 设置分隔字符
*
* @param dividerCharacter 分隔字符
*/
public void setDividerCharacter(char dividerCharacter) {
this.divideCharacter = dividerCharacter;
}
private CharSequence deleteDivideChar(CharSequence string) {
StringBuilder builder = new StringBuilder();
int length = string.length();
for (int i = 0; i < length; i++) {
if (string.charAt(i) != divideCharacter) {
builder.append(string.charAt(i));
}
}
return builder;
}
/**
* 获取去掉分隔符的内容
*
* @return 去掉了分隔符的内容
*/
public String getRealInputText() {
if (divisionEnable) {
return deleteDivideChar(getText()).toString();
} else {
return getText().toString();
}
}
private int[] getDivisionPatter() {
int len = 0;
int i = 0;
while (len < maxLength) {
if (i < formatPattern.length) {
len += formatPattern[i];
} else {
len += formatPattern[formatPattern.length - 1];
}
i++;
}
int[] arrayInt = new int[--i];
arrayInt[0] = formatPattern[0];
for (int j = 1; j < i; j++) {
arrayInt[j] = arrayInt[j - 1];
if (j < formatPattern.length) {
arrayInt[j] += formatPattern[j] + 1;
} else {
arrayInt[j] += formatPattern[formatPattern.length - 1] + 1;
}
}
return arrayInt;
}
final class Divider implements TextWatcher, InputFilter {
private int deleteDivider = 0;
public final void afterTextChanged(Editable source) {
if (isMoneyType) {
do {
if (source.length() == 1 && source.charAt(0) == '.') {
source.clear();
break;
}
if (source.length() == 2 && source.charAt(0) == '0' && !source.toString().equals("0.")) {
source.delete(0, 1);
}
int dotPos = -1;
int len = source.length();
for (int i = 0; i < len; i++) {
if (source.charAt(i) == '.') {
dotPos = i;
break;
}
}
//金额类型最多输入两位小数
if (dotPos != -1 && (dotPos + 3) < len) {
source.delete(dotPos + 3, source.length());
}
} while (false);
}
if (!divisionEnable) {
return;
}
if (this.deleteDivider > 1) {
int tmp = this.deleteDivider;
this.deleteDivider = 0;
source.delete(tmp - 1, tmp);
}
int j = 0;
for (int i = 0; i < source.length(); i++) {
char character = source.charAt(i);
if (j > divisionPattern.length - 1) {
j = divisionPattern.length - 1;
}
if (i == divisionPattern[j]) {
if (j < divisionPattern.length - 1) {
j++;
}
if (character != divideCharacter) {
source.insert(i, String.valueOf(divideCharacter));
}
} else if (character == divideCharacter) {
source.delete(i, i + 1);
i--;
}
}
}
public final void beforeTextChanged(CharSequence paramCharSequence, int paramInt1, int paramInt2, int paramInt3) {
}
public final void onTextChanged(CharSequence paramCharSequence, int paramInt1, int paramInt2, int paramInt3) {
}
public final CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (!divisionEnable) {
return source;
}
if (deleteDivideChar(
new SpannableStringBuilder(dest).replace(dstart, dend, source, start, end).toString()).length() > maxLength) {
return "";
}
SpannableStringBuilder result = new SpannableStringBuilder(source);
int[] arrayOfInt = divisionPattern;
int dLength = dend - dstart;
// if(dest.length()<=dstart)
// return result;
for (int i = 0; i < arrayOfInt.length; i++) {
if ((source.length() == 0) && (dstart == arrayOfInt[i]) && (dest.charAt(dstart) == divideCharacter)) {
this.deleteDivider = arrayOfInt[i];
}
int j;
if ((dstart - dLength <= arrayOfInt[i])
&& (dstart + end - dLength >= arrayOfInt[i])
&& (((j = arrayOfInt[i] - dstart) == end) || ((j >= 0) && (j < end) && (result.charAt(j) != divideCharacter)))) {
result.insert(j, String.valueOf(divideCharacter));
end++;
}
}
return result;
}
}
}

View File

@@ -0,0 +1,179 @@
package com.fengliyan.uikit.editor;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.fengliyan.uikit.R;
/**
* Created by ChenQihong on 2016/12/14.
*/
public class CaiEditText extends LinearLayout implements View.OnClickListener {
public static final int EDIT_TYPE_NONE = 0x00000000;
public static final int EDIT_TYPE_TEXT = 0x00000001;
public static final int EDIT_TYPE_PHONE_NUM = 0x00000003;
public static final int EDIT_TYPE_NUM_PASSWORD = 0x00000012;
private AutoDivisionEditText mEditText;
private ImageView mFunctionalImage;
private ImageView mInputLogo;
private int mInputType = EDIT_TYPE_NONE;
private OnRightFunctionButtonClickListener mRightClick;
private TextWatcher mWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (!mEditText.isEnabled()) {
mFunctionalImage.setVisibility(View.INVISIBLE);
return;
}
if (editable.length() > 0 && mEditText.isFocused()) {
mFunctionalImage.setVisibility(View.VISIBLE);
} else {
mFunctionalImage.setVisibility(View.INVISIBLE);
}
}
};
public interface OnRightFunctionButtonClickListener {
void onClick(View v);
}
public CaiEditText(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.cai_edit_text, this, true);
mEditText = (AutoDivisionEditText) findViewById(R.id.cai_edit_text);
mFunctionalImage = (ImageView) findViewById(R.id.cai_function_image);
mInputLogo = (ImageView) findViewById(R.id.cai_input_logo);
mFunctionalImage.setOnClickListener(this);
mEditText.setDivisionEnable(false);
mEditText.addTextChangedListener(mWatcher);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CaiEditText);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.CaiEditText_edit_type) {
mInputType = a.getInt(attr, EditorInfo.TYPE_NULL);
} else if (attr == R.styleable.CaiEditText_hint) {
String hint = a.getString(attr);
mEditText.setHint(hint);
} else if (attr == R.styleable.CaiEditText_left_img) {
int resId = a.getResourceId(attr, 0);
if (0 != resId) {
mInputLogo.setImageResource(resId);
}
} else if (attr == R.styleable.CaiEditText_right_img) {
int resId = a.getResourceId(attr, 0);
if (0 != resId) {
mFunctionalImage.setImageResource(resId);
}
} else if (attr == R.styleable.CaiEditText_left_img_enable) {
boolean enable = a.getBoolean(attr, true);
if (enable) {
mInputLogo.setVisibility(VISIBLE);
} else {
mInputLogo.setVisibility(GONE);
}
} else if (attr == R.styleable.CaiEditText_right_img_enable) {
boolean enable = a.getBoolean(attr, true);
if (enable) {
mFunctionalImage.setVisibility(VISIBLE);
} else {
mFunctionalImage.setVisibility(GONE);
}
} else if (attr == R.styleable.CaiEditText_hint_color) {
int colorId = a.getColor(R.styleable.CaiEditText_hint_color, getResources().getColor(R.color.cp_color_gray));
// ColorStateList colorId = a.getColorStateList(R.styleable.CaiEditText_hint_color);
// int colorId = a.getResourceId(attr, 0);
// if (0 != colorId) {
mEditText.setHintTextColor(colorId);
// }
} else if (attr == R.styleable.CaiEditText_gravity_center) {
boolean enable = a.getBoolean(attr, false);
if (enable) {
mEditText.setGravity(Gravity.CENTER);
}
}
}
if (mInputType == EDIT_TYPE_PHONE_NUM) {
mEditText.setDivisionEnable(true);
int[] newFormatType = {3, 4, 4};
mEditText.setFormatPattern(11, newFormatType);
//mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
} else {
mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(20)});
}
mEditText.setInputType(mInputType);
}
public int getInputType() {
return mInputType;
}
public void setInputType(int inputType) {
mEditText.setInputType(inputType);
this.mInputType = inputType;
}
public void setDivisionEnable(boolean enable) {
mEditText.setDivisionEnable(enable);
}
public void setRightButtonFunction(OnRightFunctionButtonClickListener rightClick) {
mRightClick = rightClick;
}
public void setRightButtonImageById(int resid) {
mFunctionalImage.setImageResource(resid);
}
public AutoDivisionEditText getEditText() {
return mEditText;
}
public void setEditText(AutoDivisionEditText editText) {
this.mEditText = editText;
}
@Override
public void onClick(View view) {
if (view == mFunctionalImage && null != mRightClick) {
mRightClick.onClick(view);
}
}
public String getText() {
return mEditText.getRealInputText();
}
public void setText(String message) {
mEditText.setText(message);
}
public void enable(boolean isEnable) {
mEditText.setEnabled(isEnable);
}
}

View File

@@ -0,0 +1,344 @@
package com.fengliyan.uikit.emoji;
import android.graphics.Bitmap;
import android.text.TextUtils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 用于把附件保存到文件系统中
*/
public class AttachmentStore {
public static long copy(String srcPath, String dstPath) {
if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(dstPath)) {
return -1;
}
File source = new File(srcPath);
if (!source.exists()) {
return -1;
}
if (srcPath.equals(dstPath)) {
return source.length();
}
FileChannel fcin = null;
FileChannel fcout = null;
try {
fcin = new FileInputStream(source).getChannel();
fcout = new FileOutputStream(create(dstPath)).getChannel();
ByteBuffer tmpBuffer = ByteBuffer.allocateDirect(4096);
while (fcin.read(tmpBuffer) != -1) {
tmpBuffer.flip();
fcout.write(tmpBuffer);
tmpBuffer.clear();
}
return source.length();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fcin != null) {
fcin.close();
}
if (fcout != null) {
fcout.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return -1;
}
public static long getFileLength(String srcPath) {
if (TextUtils.isEmpty(srcPath)) {
return -1;
}
File srcFile = new File(srcPath);
if (!srcFile.exists()) {
return -1;
}
return srcFile.length();
}
public static long save(String path, String content) {
return save(content.getBytes(), path);
}
/**
* 把数据保存到文件系统中,并且返回其大小
*
* @param data
* @param filePath
* @return 如果保存失败, 则返回-1
*/
public static long save(byte[] data, String filePath) {
if (TextUtils.isEmpty(filePath)) {
return -1;
}
File f = new File(filePath);
if (f.getParentFile() == null) {
return -1;
}
if (!f.getParentFile().exists()) {// 如果不存在上级文件夹
f.getParentFile().mkdirs();
}
try {
f.createNewFile();
FileOutputStream fout = new FileOutputStream(f);
fout.write(data);
fout.close();
} catch (IOException e) {
e.printStackTrace();
return -1;
}
return f.length();
}
public static boolean move(String srcFilePath, String dstFilePath) {
if (TextUtils.isEmpty(srcFilePath) || TextUtils.isEmpty(dstFilePath)) {
return false;
}
File srcFile = new File(srcFilePath);
if (!srcFile.exists() || !srcFile.isFile()) {
return false;
}
File dstFile = new File(dstFilePath);
if (dstFile.getParentFile() == null) {
return false;
}
if (!dstFile.getParentFile().exists()) {// 如果不存在上级文件夹
dstFile.getParentFile().mkdirs();
}
return srcFile.renameTo(dstFile);
}
public static File create(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return null;
}
File f = new File(filePath);
if (!f.getParentFile().exists()) {// 如果不存在上级文件夹
f.getParentFile().mkdirs();
}
try {
f.createNewFile();
return f;
} catch (IOException e) {
if (f != null && f.exists()) {
f.delete();
}
return null;
}
}
/**
* @param is
* @param filePath
* @return 保存失败,返回-1
*/
public static long save(InputStream is, String filePath) {
File f = new File(filePath);
if (!f.getParentFile().exists()) {// 如果不存在上级文件夹
f.getParentFile().mkdirs();
}
FileOutputStream fos = null;
try {
f.createNewFile();
fos = new FileOutputStream(f);
int read = 0;
byte[] bytes = new byte[8091];
while ((read = is.read(bytes)) != -1) {
fos.write(bytes, 0, read);
}
return f.length();
} catch (IOException e) {
if (f != null && f.exists()) {
f.delete();
}
return -1;
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 把文件从文件系统中读取出来
*
* @param path
* @return 如果无法读取, 则返回null
*/
public static byte[] load(String path) {
try {
File f = new File(path);
int unread = (int) f.length();
int read = 0;
byte[] buf = new byte[unread]; // 读取文件长度
FileInputStream fin = new FileInputStream(f);
do {
int count = fin.read(buf, read, unread);
read += count;
unread -= count;
} while (unread != 0);
fin.close();
return buf;
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
return null;
}
}
public static String loadAsString(String path) {
if (isFileExist(path)) {
byte[] content = load(path);
return new String(content);
} else {
return null;
}
}
/**
* 删除指定路径文件
*
* @param path
*/
public static boolean delete(String path) {
if (TextUtils.isEmpty(path)) {
return false;
}
File f = new File(path);
if (f.exists()) {
f = renameOnDelete(f);
return f.delete();
} else {
return false;
}
}
public static void deleteOnExit(String path) {
if (TextUtils.isEmpty(path)) {
return;
}
File f = new File(path);
if (f.exists()) {
f.deleteOnExit();
}
}
public static boolean deleteDir(String path) {
return deleteDir(path, true);
}
private static boolean deleteDir(String path, boolean rename) {
boolean success = true;
File file = new File(path);
if (file.exists()) {
if (rename) {
file = renameOnDelete(file);
}
File[] list = file.listFiles();
if (list != null) {
int len = list.length;
for (int i = 0; i < len; ++i) {
if (list[i].isDirectory()) {
deleteDir(list[i].getPath(), false);
} else {
boolean ret = list[i].delete();
if (!ret) {
success = false;
}
}
}
}
} else {
success = false;
}
if (success) {
file.delete();
}
return success;
}
// rename before delete to avoid lingering filesystem lock of android
private static File renameOnDelete(File file) {
String tmpPath = file.getParent() + "/" + System.currentTimeMillis() + "_tmp";
File tmpFile = new File(tmpPath);
if (file.renameTo(tmpFile)) {
return tmpFile;
} else {
return file;
}
}
public static boolean isFileExist(String path) {
if (!TextUtils.isEmpty(path) && new File(path).exists()) {
return true;
} else {
return false;
}
}
public static boolean saveBitmap(Bitmap bitmap, String path, boolean recycle) {
if (bitmap == null || TextUtils.isEmpty(path)) {
return false;
}
BufferedOutputStream bos = null;
try {
FileOutputStream fos = new FileOutputStream(path);
bos = new BufferedOutputStream(fos);
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);
return true;
} catch (FileNotFoundException e) {
return false;
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
}
}
if (recycle) {
bitmap.recycle();
}
}
}
}

View File

@@ -0,0 +1,5 @@
package com.fengliyan.uikit.emoji;
public interface IEmoticonCategoryChanged {
void onCategoryChanged(int index);
}

View File

@@ -0,0 +1,7 @@
package com.fengliyan.uikit.emoji;
public interface IEmoticonSelectedListener {
void onEmojiSelected(String key);
void onStickerSelected(String categoryName, String stickerName);
}

View File

@@ -0,0 +1,89 @@
package com.fengliyan.uikit.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.style.ImageSpan;
import java.lang.ref.WeakReference;
/**
*
*/
public class ImageSpanAlignCenter extends ImageSpan {
private static final char[] ELLIPSIS_NORMAL = {'\u2026'};
private static final char[] ELLIPSIS_TWO_DOTS = {'\u2025'};
public ImageSpanAlignCenter(Context context, int resourceId) {
super(context, resourceId);
}
public ImageSpanAlignCenter(Drawable d) {
super(d);
}
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable(paint);
Rect rect = d.getBounds();
if (fm != null) {
Paint.FontMetricsInt fontMetrics = new Paint.FontMetricsInt();
paint.getFontMetricsInt(fontMetrics);
fm.ascent = fontMetrics.ascent;
fm.descent = fontMetrics.descent;
fm.top = fontMetrics.top;
fm.bottom = fontMetrics.bottom;
}
return rect.right;
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
int bottom, Paint paint) {
final String s = text.toString();
String subS = s.substring(start, end);
if (ELLIPSIS_NORMAL[0] == subS.charAt(0)
|| ELLIPSIS_TWO_DOTS[0] == subS.charAt(0)) {
canvas.save();
canvas.drawText(subS, x, y, paint);
canvas.restore();
} else {
Drawable d = getCachedDrawable(paint);
canvas.save();
int transY;
Paint.FontMetricsInt fontMetrics = new Paint.FontMetricsInt();
paint.getFontMetricsInt(fontMetrics);
transY = y + fontMetrics.ascent;
canvas.translate(x, transY);
d.draw(canvas);
canvas.restore();
}
}
private Drawable getCachedDrawable(Paint paint) {
WeakReference<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null)
d = wr.get();
if (d == null) {
d = getDrawable();
d.setBounds(new Rect(0, 0, paint.getFontMetricsInt(null), paint.getFontMetricsInt(null)));
mDrawableRef = new WeakReference<Drawable>(d);
}
return d;
}
private WeakReference<Drawable> mDrawableRef;
}

View File

@@ -0,0 +1,135 @@
package com.fengliyan.uikit.emoji;
import android.content.Context;
import android.content.res.AssetManager;
import com.fengliyan.base.base.ContextHolder;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class StickerCategory implements Serializable {
private static final long serialVersionUID = -81692490861539040L;
private String name; // 贴纸包名
private String title; // 显示的标题
private boolean system; // 是否是系统内置表情
private int order = 0; // 默认顺序
private transient List<StickerItem> stickers;
public StickerCategory(String name, String title, boolean system, int order) {
this.title = title;
this.name = name;
this.system = system;
this.order = order;
loadStickerData();
}
public boolean system() {
return system;
}
public void setSystem(boolean system) {
this.system = system;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<StickerItem> getStickers() {
return stickers;
}
public boolean hasStickers() {
return stickers != null && stickers.size() > 0;
}
public InputStream getCoverNormalInputStream(Context context) {
String filename = name + "_s_normal.png";
return makeFileInputStream(context, filename);
}
public InputStream getCoverPressedInputStream(Context context) {
String filename = name + "_s_pressed.png";
return makeFileInputStream(context, filename);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getCount() {
if (stickers == null || stickers.isEmpty()) {
return 0;
}
return stickers.size();
}
public int getOrder() {
return order;
}
private InputStream makeFileInputStream(Context context, String filename) {
try {
if (system) {
AssetManager assetManager = context.getResources().getAssets();
String path = "sticker/" + filename;
return assetManager.open(path);
} else {
// for future
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public List<StickerItem> loadStickerData() {
List<StickerItem> stickers = new ArrayList<>();
AssetManager assetManager = ContextHolder.getApplicationContext().getResources().getAssets();
try {
String[] files = assetManager.list("sticker/" + name);
for (String file : files) {
stickers.add(new StickerItem(name, file));
}
} catch (IOException e) {
e.printStackTrace();
}
this.stickers = stickers;
return stickers;
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof StickerCategory)) {
return false;
}
if (o == this) {
return true;
}
StickerCategory r = (StickerCategory) o;
return r.getName().equals(getName());
}
@Override
public int hashCode() {
return name.hashCode();
}
}

View File

@@ -0,0 +1,33 @@
package com.fengliyan.uikit.emoji;
public class StickerItem {
private String category;//类别名
private String name;
public StickerItem(String category, String name) {
this.category = category;
this.name = name;
}
public String getIdentifier() {
return category + "/" + name;
}
public String getCategory() {
return category;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (o != null && o instanceof StickerItem) {
StickerItem item = (StickerItem) o;
return item.getCategory().equals(category) && item.getName().equals(name);
}
return false;
}
}

View File

@@ -0,0 +1,56 @@
package com.fengliyan.uikit.emoji.manager;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.emoji.view.EmoticonView;
public class EmojiAdapter extends BaseAdapter {
private Context context;
private int startIndex;
public EmojiAdapter(Context mContext, int startIndex) {
this.context = mContext;
this.startIndex = startIndex;
}
public int getCount() {
int count = EmojiManager.getDisplayCount() - startIndex + 1;
count = Math.min(count, EmoticonView.EMOJI_PER_PAGE + 1);
return count;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return startIndex + position;
}
@SuppressLint({"ViewHolder", "InflateParams"})
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(context).inflate(R.layout.nim_emoji_item, null);
ImageView emojiThumb = (ImageView) convertView.findViewById(R.id.imgEmoji);
int count = EmojiManager.getDisplayCount();
int index = startIndex + position;
if (position == EmoticonView.EMOJI_PER_PAGE || index == count) {
emojiThumb.setBackgroundResource(R.drawable.nim_emoji_del);
} else if (index < count) {
emojiThumb.setBackgroundDrawable(EmojiManager.getDisplayDrawable(context, index));
}
return convertView;
}
}

View File

@@ -0,0 +1,194 @@
package com.fengliyan.uikit.emoji.manager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.LruCache;
import android.util.Xml;
import com.fengliyan.base.base.ContextHolder;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class EmojiManager {
private static final String EMOT_DIR = "emoji/";
// max cache size
private static final int CACHE_MAX_SIZE = 1024;
private static Pattern pattern;
// default entries
private static final List<Entry> defaultEntries = new ArrayList<Entry>();
// text to entry
private static final Map<String, Entry> text2entry = new HashMap<String, Entry>();
// asset bitmap cache, key: asset path
private static LruCache<String, Bitmap> drawableCache;
static {
Context context = ContextHolder.getApplicationContext();
load(context, EMOT_DIR + "emoji.xml");
pattern = makePattern();
drawableCache = new LruCache<String, Bitmap>(CACHE_MAX_SIZE) {
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != newValue)
oldValue.recycle();
}
};
}
private static class Entry {
String text;
String assetPath;
Entry(String text, String assetPath) {
this.text = text;
this.assetPath = assetPath;
}
}
//
// display
//
public static final int getDisplayCount() {
return defaultEntries.size();
}
public static final Drawable getDisplayDrawable(Context context, int index) {
String text = (index >= 0 && index < defaultEntries.size() ?
defaultEntries.get(index).text : null);
return text == null ? null : getDrawable(context, text);
}
public static final String getDisplayText(int index) {
return index >= 0 && index < defaultEntries.size() ? defaultEntries
.get(index).text : null;
}
public static final Pattern getPattern() {
return pattern;
}
public static final Drawable getDrawable(Context context, String text) {
Entry entry = text2entry.get(text);
if (entry == null) {
return null;
}
Bitmap cache = drawableCache.get(entry.assetPath);
if (cache == null) {
cache = loadAssetBitmap(context, entry.assetPath);
}
return new BitmapDrawable(context.getResources(), cache);
}
//
// internal
//
private static Pattern makePattern() {
return Pattern.compile(patternOfDefault());
}
private static String patternOfDefault() {
return "\\[[^\\[]{1,10}\\]";
}
private static Bitmap loadAssetBitmap(Context context, String assetPath) {
InputStream is = null;
try {
Resources resources = context.getResources();
Options options = new Options();
options.inDensity = DisplayMetrics.DENSITY_HIGH;
options.inScreenDensity = resources.getDisplayMetrics().densityDpi;
options.inTargetDensity = resources.getDisplayMetrics().densityDpi;
is = context.getAssets().open(assetPath);
Bitmap bitmap = BitmapFactory.decodeStream(is, new Rect(), options);
if (bitmap != null) {
drawableCache.put(assetPath, bitmap);
}
return bitmap;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private static final void load(Context context, String xmlPath) {
new EntryLoader().load(context, xmlPath);
}
//
// load emoticons from asset
//
private static class EntryLoader extends DefaultHandler {
private String catalog = "";
void load(Context context, String assetPath) {
InputStream is = null;
try {
is = context.getAssets().open(assetPath);
Xml.parse(is, Xml.Encoding.UTF_8, this);
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (localName.equals("Catalog")) {
catalog = attributes.getValue(uri, "Title");
} else if (localName.equals("Emoticon")) {
String tag = attributes.getValue(uri, "Tag");
String fileName = attributes.getValue(uri, "File");
Entry entry = new Entry(tag, EMOT_DIR + catalog + "/" + fileName);
text2entry.put(entry.text, entry);
if (catalog.equals("default")) {
defaultEntries.add(entry);
}
}
}
}
}

View File

@@ -0,0 +1,89 @@
package com.fengliyan.uikit.emoji.manager;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.emoji.StickerCategory;
import com.fengliyan.uikit.emoji.StickerItem;
import com.fengliyan.uikit.emoji.view.EmoticonView;
/**
* 每屏显示的贴图
*/
public class StickerAdapter extends BaseAdapter {
private Context context;
private StickerCategory category;
private int startIndex;
public StickerAdapter(Context mContext, StickerCategory category, int startIndex) {
this.context = mContext;
this.category = category;
this.startIndex = startIndex;
}
public int getCount() {//获取每一页的数量
int count = category.getStickers().size() - startIndex;
count = Math.min(count, EmoticonView.STICKER_PER_PAGE);
return count;
}
@Override
public Object getItem(int position) {
return category.getStickers().get(startIndex + position);
}
@Override
public long getItemId(int position) {
return startIndex + position;
}
public View getView(int position, View convertView, ViewGroup parent) {
StickerViewHolder viewHolder;
if (convertView == null) {
convertView = View.inflate(context, R.layout.nim_sticker_picker_view, null);
viewHolder = new StickerViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.sticker_thumb_image);
viewHolder.descLabel = (TextView) convertView.findViewById(R.id.sticker_desc_label);
convertView.setTag(viewHolder);
} else {
viewHolder = (StickerViewHolder) convertView.getTag();
}
int index = startIndex + position;
if (index >= category.getStickers().size()) {
return convertView;
}
StickerItem sticker = category.getStickers().get(index);
if (sticker == null) {
return convertView;
}
Glide.with(context)
.load(StickerManager.getInstance().getStickerUri(sticker.getCategory(), sticker.getName()))
.apply(new RequestOptions()
.error(R.drawable.nim_default_img_failed)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate())
.into(viewHolder.imageView);
viewHolder.descLabel.setVisibility(View.GONE);
return convertView;
}
class StickerViewHolder {
public ImageView imageView;
public TextView descLabel;
}
}

View File

@@ -0,0 +1,124 @@
package com.fengliyan.uikit.emoji.manager;
import android.content.res.AssetManager;
import android.util.Log;
import com.fengliyan.base.base.ContextHolder;
import com.fengliyan.base.base.utils.FileUtil;
import com.fengliyan.uikit.emoji.StickerCategory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 贴图管理类
*/
public class StickerManager {
private static final String TAG = "StickerManager";
private static StickerManager instance;
private static final String CATEGORY_AJMD = "ajmd";
private static final String CATEGORY_XXY = "xxy";
private static final String CATEGORY_LT = "lt";
/**
* 数据源
*/
private List<StickerCategory> stickerCategories = new ArrayList<>();
private Map<String, StickerCategory> stickerCategoryMap = new HashMap<>();
private Map<String, Integer> stickerOrder = new HashMap<>(3);
public static StickerManager getInstance() {
if (instance == null) {
instance = new StickerManager();
}
return instance;
}
public StickerManager() {
initStickerOrder();
loadStickerCategory();
}
public void init() {
Log.i(TAG, "Sticker Manager init...");
}
private void initStickerOrder() {
// 默认贴图顺序
stickerOrder.put(CATEGORY_AJMD, 1);
stickerOrder.put(CATEGORY_XXY, 2);
stickerOrder.put(CATEGORY_LT, 3);
}
private boolean isSystemSticker(String category) {
return CATEGORY_XXY.equals(category) ||
CATEGORY_AJMD.equals(category) ||
CATEGORY_LT.equals(category);
}
private int getStickerOrder(String categoryName) {
if (stickerOrder.containsKey(categoryName)) {
return stickerOrder.get(categoryName);
} else {
return 100;
}
}
private void loadStickerCategory() {
AssetManager assetManager = ContextHolder.getApplicationContext().getResources().getAssets();
try {
String[] files = assetManager.list("sticker");
StickerCategory category;
for (String name : files) {
if (!FileUtil.hasExtentsion(name)) {
category = new StickerCategory(name, name, true, getStickerOrder(name));
stickerCategories.add(category);
stickerCategoryMap.put(name, category);
}
}
// 排序
Collections.sort(stickerCategories, new Comparator<StickerCategory>() {
@Override
public int compare(StickerCategory l, StickerCategory r) {
return l.getOrder() - r.getOrder();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
public synchronized List<StickerCategory> getCategories() {
return stickerCategories;
}
public synchronized StickerCategory getCategory(String name) {
return stickerCategoryMap.get(name);
}
public String getStickerUri(String categoryName, String stickerName) {
StickerManager manager = StickerManager.getInstance();
StickerCategory category = manager.getCategory(categoryName);
if (category == null) {
return null;
}
if (isSystemSticker(categoryName)) {
if (!stickerName.contains(".png")) {
stickerName += ".png";
}
String path = "sticker/" + category.getName() + "/" + stickerName;
return "file:///android_asset/" + path;
}
return null;
}
}

View File

@@ -0,0 +1,270 @@
package com.fengliyan.uikit.emoji.utils;
import android.annotation.TargetApi;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.os.Build;
import android.provider.MediaStore;
import com.fengliyan.uikit.emoji.AttachmentStore;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class BitmapDecoder {
public static Bitmap decode(InputStream is) {
BitmapFactory.Options options = new BitmapFactory.Options();
// RGB_565
options.inPreferredConfig = Bitmap.Config.RGB_565;
/**
* 在4.4上如果之前is标记被移动过会导致解码失败
*/
try {
if (is.markSupported()) {
is.reset();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
return BitmapFactory.decodeStream(is, null, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
return null;
}
public static Bitmap decodeSampledForDisplay(String pathName) {
return decodeSampledForDisplay(pathName, true);
}
public static Bitmap decodeSampledForDisplay(String pathName, boolean withTextureLimit) {
float ratio = 5f;
int[][] reqBounds = new int[][]{
new int[]{ScreenUtil.screenWidth * 2, ScreenUtil.screenHeight},
new int[]{ScreenUtil.screenWidth, ScreenUtil.screenHeight * 2},
new int[]{(int) (ScreenUtil.screenWidth * 1.414), (int) (ScreenUtil.screenHeight * 1.414)},
};
// decode bound
int[] bound = decodeBound(pathName);
// pick request bound
int[] reqBound = pickReqBoundWithRatio(bound, reqBounds, ratio);
int width = bound[0];
int height = bound[1];
int reqWidth = reqBound[0];
int reqHeight = reqBound[1];
// calculate sample size
int sampleSize = SampleSizeUtil.calculateSampleSize(width, height, reqWidth, reqHeight);
if (withTextureLimit) {
// adjust sample size
sampleSize = SampleSizeUtil.adjustSampleSizeWithTexture(sampleSize, width, height);
}
int RETRY_LIMIT = 5;
Bitmap bitmap = decodeSampled(pathName, sampleSize);
while (bitmap == null && RETRY_LIMIT > 0) {
sampleSize++;
RETRY_LIMIT--;
bitmap = decodeSampled(pathName, sampleSize);
}
return bitmap;
}
public static int[] decodeBound(String pathName) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
return new int[]{options.outWidth, options.outHeight};
}
public static int[] decodeBound(Resources res, int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
return new int[]{options.outWidth, options.outHeight};
}
private static int[] pickReqBoundWithRatio(int[] bound, int[][] reqBounds, float ratio) {
float hRatio = bound[1] == 0 ? 0 : (float) bound[0] / (float) bound[1];
float vRatio = bound[0] == 0 ? 0 : (float) bound[1] / (float) bound[0];
if (hRatio >= ratio) {
return reqBounds[0];
} else if (vRatio >= ratio) {
return reqBounds[1];
} else {
return reqBounds[2];
}
}
public static Bitmap decodeSampled(String pathName, int sampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
// RGB_565
options.inPreferredConfig = Bitmap.Config.RGB_565;
// sample size
options.inSampleSize = sampleSize;
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(pathName, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
return null;
}
return checkInBitmap(bitmap, options, pathName);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static Bitmap checkInBitmap(Bitmap bitmap,
BitmapFactory.Options options, String path) {
boolean honeycomb = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
if (honeycomb && bitmap != options.inBitmap && options.inBitmap != null) {
options.inBitmap.recycle();
options.inBitmap = null;
}
if (bitmap == null) {
try {
bitmap = BitmapFactory.decodeFile(path, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
}
return bitmap;
}
public static int[] decodeBound(File file) {
InputStream is = null;
try {
is = new FileInputStream(file);
int[] bound = decodeBound(is);
return bound;
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new int[]{0, 0};
}
public static int[] decodeBound(InputStream is) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
return new int[]{options.outWidth, options.outHeight};
}
public static Bitmap decodeSampled(InputStream is, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
// RGB_565
options.inPreferredConfig = Bitmap.Config.RGB_565;
// sample size
options.inSampleSize = getSampleSize(is, reqWidth, reqHeight);
try {
return BitmapFactory.decodeStream(is, null, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
return null;
}
public static Bitmap decodeSampled(String pathName, int reqWidth, int reqHeight) {
return decodeSampled(pathName, getSampleSize(pathName, reqWidth, reqHeight));
}
public static int getSampleSize(InputStream is, int reqWidth, int reqHeight) {
// decode bound
int[] bound = decodeBound(is);
// calculate sample size
int sampleSize = SampleSizeUtil.calculateSampleSize(bound[0], bound[1], reqWidth, reqHeight);
return sampleSize;
}
public static int getSampleSize(String pathName, int reqWidth, int reqHeight) {
// decode bound
int[] bound = decodeBound(pathName);
// calculate sample size
int sampleSize = SampleSizeUtil.calculateSampleSize(bound[0], bound[1], reqWidth, reqHeight);
return sampleSize;
}
/**
* ******************************* decode resource ******************************************
*/
public static Bitmap decodeSampled(Resources resources, int resId, int reqWidth, int reqHeight) {
return decodeSampled(resources, resId, getSampleSize(resources, resId, reqWidth, reqHeight));
}
public static int getSampleSize(Resources resources, int resId, int reqWidth, int reqHeight) {
// decode bound
int[] bound = decodeBound(resources, resId);
// calculate sample size
int sampleSize = SampleSizeUtil.calculateSampleSize(bound[0], bound[1], reqWidth, reqHeight);
return sampleSize;
}
public static Bitmap decodeSampled(Resources res, int resId, int sampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
// RGB_565
options.inPreferredConfig = Bitmap.Config.RGB_565;
// sample size
options.inSampleSize = sampleSize;
try {
return BitmapFactory.decodeResource(res, resId, options);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
return null;
}
public static boolean extractThumbnail(String videoPath, String thumbPath) {
if (!AttachmentStore.isFileExist(thumbPath)) {
Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Images.Thumbnails.MINI_KIND);
if (thumbnail != null) {
AttachmentStore.saveBitmap(thumbnail, thumbPath, true);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,229 @@
package com.fengliyan.uikit.emoji.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import com.fengliyan.uikit.emoji.manager.EmojiManager;
import com.fengliyan.uikit.emoji.ImageSpanAlignCenter;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MoonUtil {
private static final float DEF_SCALE = 0.6f;
private static final float SMALL_SCALE = 0.45F;
public static void identifyFaceExpression(Context context,
View textView, String value, int align) {
identifyFaceExpression(context, textView, value, align, DEF_SCALE);
}
public static void identifyFaceExpressionAndATags(Context context,
View textView, String value, int align) {
SpannableString mSpannableString = makeSpannableStringTags(context, value, DEF_SCALE, align);
viewSetText(textView, mSpannableString);
}
/**
* 具体类型的view设置内容
*
* @param textView
* @param mSpannableString
*/
private static void viewSetText(View textView, SpannableString mSpannableString) {
if (textView instanceof TextView) {
TextView tv = (TextView) textView;
tv.setText(mSpannableString);
} else if (textView instanceof EditText) {
EditText et = (EditText) textView;
et.setText(mSpannableString);
}
}
public static void identifyFaceExpression(Context context,
View textView, String value, int align, float scale) {
SpannableString mSpannableString = replaceEmoticons(context, value, scale, align);
viewSetText(textView, mSpannableString);
}
/**
* lstmsgviewholder类使用,只需显示a标签对应的文本
*/
public static void identifyFaceExpressionAndTags(Context context,
View textView, String value, int align, float scale) {
SpannableString mSpannableString = makeSpannableStringTags(context, value, scale, align, false);
viewSetText(textView, mSpannableString);
}
private static SpannableString replaceEmoticons(Context context, String value, float scale, int align) {
if (TextUtils.isEmpty(value)) {
value = "";
}
SpannableString mSpannableString = new SpannableString(value);
Matcher matcher = EmojiManager.getPattern().matcher(value);
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
String emot = value.substring(start, end);
Drawable d = getEmotDrawable(context, emot, scale);
if (d != null) {
ImageSpan span = new ImageSpan(d, align);
mSpannableString.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return mSpannableString;
}
private static Pattern mATagPattern = Pattern.compile("<a.*?>.*?</a>");
public static SpannableString makeSpannableStringTags(Context context, String value, float scale, int align) {
return makeSpannableStringTags(context, value, DEF_SCALE, align, true);
}
public static SpannableString makeSpannableStringTags(Context context, String value, float scale, int align, boolean bTagClickable) {
ArrayList<ATagSpan> tagSpans = new ArrayList<ATagSpan>();
if (TextUtils.isEmpty(value)) {
value = "";
}
//a标签需要替换原始文本,放在moonutil类中
Matcher aTagMatcher = mATagPattern.matcher(value);
int start = 0;
int end = 0;
while (aTagMatcher.find()) {
start = aTagMatcher.start();
end = aTagMatcher.end();
String atagString = value.substring(start, end);
ATagSpan tagSpan = getTagSpan(atagString);
value = value.substring(0, start) + tagSpan.getTag() + value.substring(end);
tagSpan.setRange(start, start + tagSpan.getTag().length());
tagSpans.add(tagSpan);
aTagMatcher = mATagPattern.matcher(value);
}
SpannableString mSpannableString = new SpannableString(value);
Matcher matcher = EmojiManager.getPattern().matcher(value);
while (matcher.find()) {
start = matcher.start();
end = matcher.end();
String emot = value.substring(start, end);
Drawable d = getEmotDrawable(context, emot, scale);
if (d != null) {
ImageSpan span = align == -1 ? new ImageSpanAlignCenter(d) : new ImageSpan(d, align);
mSpannableString.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
if (bTagClickable) {
for (ATagSpan tagSpan : tagSpans) {
mSpannableString.setSpan(tagSpan, tagSpan.start, tagSpan.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return mSpannableString;
}
public static void replaceEmoticons(Context context, Editable editable, int start, int count) {
if (count <= 0 || editable.length() < start + count)
return;
CharSequence s = editable.subSequence(start, start + count);
Matcher matcher = EmojiManager.getPattern().matcher(s);
while (matcher.find()) {
int from = start + matcher.start();
int to = start + matcher.end();
String emot = editable.subSequence(from, to).toString();
Drawable d = getEmotDrawable(context, emot, SMALL_SCALE);
if (d != null) {
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM);
editable.setSpan(span, from, to, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private static Drawable getEmotDrawable(Context context, String text, float scale) {
Drawable drawable = EmojiManager.getDrawable(context, text);
// scale
if (drawable != null) {
int width = (int) (drawable.getIntrinsicWidth() * scale);
int height = (int) (drawable.getIntrinsicHeight() * scale);
drawable.setBounds(0, 0, width, height);
}
return drawable;
}
private static ATagSpan getTagSpan(String text) {
String href = null;
String tag = null;
if (text.toLowerCase().contains("href")) {
int start = text.indexOf("\"");
int end = text.indexOf("\"", start + 1);
if (end > start)
href = text.substring(start + 1, end);
}
int start = text.indexOf(">");
int end = text.indexOf("<", start);
if (end > start)
tag = text.substring(start + 1, end);
return new ATagSpan(tag, href);
}
private static class ATagSpan extends ClickableSpan {
private int start;
private int end;
private String mUrl;
private String tag;
ATagSpan(String tag, String url) {
this.tag = tag;
this.mUrl = url;
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(true);
}
public String getTag() {
return tag;
}
public void setRange(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void onClick(View widget) {
try {
if (TextUtils.isEmpty(mUrl))
return;
Uri uri = Uri.parse(mUrl);
String scheme = uri.getScheme();
if (TextUtils.isEmpty(scheme)) {
mUrl = "http://" + mUrl;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,133 @@
package com.fengliyan.uikit.emoji.utils;
import android.opengl.GLES10;
public class SampleSizeUtil {
public static int calculateSampleSize(String imagePath, int totalPixel) {
int[] bound = BitmapDecoder.decodeBound(imagePath);
return calculateSampleSize(bound[0], bound[1], totalPixel);
}
public static int calculateSampleSize(int width, int height, int totalPixel) {
int ratio = 1;
if (width > 0 && height > 0) {
ratio = (int) Math.sqrt((float) (width * height) / totalPixel);
if (ratio < 1) {
ratio = 1;
}
}
return ratio;
}
/**
* Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options}
* object when decoding bitmaps using the decode* methods from
* {@link android.graphics.BitmapFactory}. This implementation calculates the closest
* inSampleSize that will result in the final decoded bitmap having a width
* and height equal to or larger than the requested width and height. This
* implementation does not ensure a power of 2 is returned for inSampleSize
* which can be faster when decoding but results in a larger bitmap which
* isn't as useful for caching purposes.
*
* @param width
* @param height
* @param reqWidth
* @param reqHeight
* @return
*/
public static int calculateSampleSize(int width, int height, int reqWidth, int reqHeight) {
// can't proceed
if (width <= 0 || height <= 0) {
return 1;
}
// can't proceed
if (reqWidth <= 0 && reqHeight <= 0) {
return 1;
} else if (reqWidth <= 0) {
reqWidth = (int) (width * reqHeight / (float) height + 0.5f);
} else if (reqHeight <= 0) {
reqHeight = (int) (height * reqWidth / (float) width + 0.5f);
}
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee a final image
// with both dimensions larger than or equal to the requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
if (inSampleSize == 0) {
inSampleSize = 1;
}
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger
// inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down
// further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
public static final int adjustSampleSizeWithTexture(int sampleSize, int width, int height) {
int textureSize = getTextureSize();
if ((textureSize > 0) && ((width > sampleSize) || (height > sampleSize))) {
while ((width / (float) sampleSize) > textureSize || (height / (float) sampleSize) > textureSize) {
sampleSize++;
}
// 2的指数对齐
sampleSize = SampleSizeUtil.roundup2n(sampleSize);
}
return sampleSize;
}
private static int textureSize = 0;
//存在第二次拿拿不到的情况所以把拿到的数据用一个static变量保存下来
public static final int getTextureSize() {
if (textureSize > 0) {
return textureSize;
}
int[] params = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, params, 0);
textureSize = params[0];
return textureSize;
}
// 将x向上对齐到2的幂指数
private static final int roundup2n(int x) {
if ((x & (x - 1)) == 0) {
return x;
}
int pos = 0;
while (x > 0) {
x >>= 1;
++pos;
}
return 1 << pos;
}
}

View File

@@ -0,0 +1,130 @@
package com.fengliyan.uikit.emoji.utils;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.Log;
import com.fengliyan.base.base.ContextHolder;
import java.lang.reflect.Field;
public class ScreenUtil {
private static final String TAG = "Demo.ScreenUtil";
private static double RATIO = 0.85;
public static int screenWidth;
public static int screenHeight;
public static int screenMin;// 宽高中,小的一边
public static int screenMax;// 宽高中,较大的值
public static float density;
public static float scaleDensity;
public static float xdpi;
public static float ydpi;
public static int densityDpi;
public static int dialogWidth;
public static int statusbarheight;
public static int navbarheight;
static {
init(ContextHolder.getApplicationContext());
}
public static int dip2px(float dipValue) {
return (int) (dipValue * density + 0.5f);
}
public static int px2dip(float pxValue) {
return (int) (pxValue / density + 0.5f);
}
public static int sp2px(float spValue) {
return (int) (spValue * scaleDensity + 0.5f);
}
public static int getDialogWidth() {
dialogWidth = (int) (screenMin * RATIO);
return dialogWidth;
}
public static void init(Context context) {
if (null == context) {
return;
}
DisplayMetrics dm = context.getApplicationContext().getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
screenMin = (screenWidth > screenHeight) ? screenHeight : screenWidth;
density = dm.density;
scaleDensity = dm.scaledDensity;
xdpi = dm.xdpi;
ydpi = dm.ydpi;
densityDpi = dm.densityDpi;
Log.d(TAG, "screenWidth=" + screenWidth + " screenHeight=" + screenHeight + " density=" + density);
}
public static int getDisplayWidth() {
if (screenWidth == 0) {
GetInfo(ContextHolder.getApplicationContext());
}
return screenWidth;
}
public static int getDisplayHeight() {
if (screenHeight == 0) {
GetInfo(ContextHolder.getApplicationContext());
}
return screenHeight;
}
public static void GetInfo(Context context) {
if (null == context) {
return;
}
DisplayMetrics dm = context.getApplicationContext().getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
screenMin = (screenWidth > screenHeight) ? screenHeight : screenWidth;
screenMax = (screenWidth < screenHeight) ? screenHeight : screenWidth;
density = dm.density;
scaleDensity = dm.scaledDensity;
xdpi = dm.xdpi;
ydpi = dm.ydpi;
densityDpi = dm.densityDpi;
statusbarheight = getStatusBarHeight(context);
navbarheight = getNavBarHeight(context);
Log.d(TAG, "screenWidth=" + screenWidth + " screenHeight=" + screenHeight + " density=" + density);
}
public static int getStatusBarHeight(Context context) {
if (statusbarheight == 0) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
int x = (Integer) field.get(o);
statusbarheight = context.getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
}
if (statusbarheight == 0) {
statusbarheight = ScreenUtil.dip2px(25);
}
return statusbarheight;
}
public static int getNavBarHeight(Context context) {
Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
return 0;
}
}

View File

@@ -0,0 +1,108 @@
package com.fengliyan.uikit.emoji.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageButton;
/**
* 选中图片控件
*/
public class CheckedImageButton extends ImageButton {
private boolean checked;
private int normalBkResId;
private int checkedBkResId;
private Drawable normalImage;
private Drawable checkedImage;
private int leftPadding, topPadding, rightPadding, bottomPadding;
public CheckedImageButton(Context context) {
super(context);
}
public CheckedImageButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CheckedImageButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setPaddingValue(int value) {
setPaddingValue(value, value, value, value);
}
public void setPaddingValue(int left, int top, int right, int bottom) {
leftPadding = left;
topPadding = top;
rightPadding = right;
bottomPadding = bottom;
setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
}
public boolean isChecked() {
return checked;
}
public void setChecked(boolean push) {
this.checked = push;
Drawable image = push ? checkedImage : normalImage;
if (image != null) {
updateImage(image);
}
int background = push ? checkedBkResId : normalBkResId;
if (background != 0) {
updateBackground(background);
}
}
public void setNormalBkResId(int normalBkResId) {
this.normalBkResId = normalBkResId;
updateBackground(normalBkResId);
}
public void setCheckedBkResId(int checkedBkResId) {
this.checkedBkResId = checkedBkResId;
}
public void setNormalImageId(int normalResId) {
normalImage = getResources().getDrawable(normalResId);
updateImage(normalImage);
}
public void setCheckedImageId(int pushedResId) {
checkedImage = getResources().getDrawable(pushedResId);
}
public void setNormalImage(Bitmap bitmap) {
this.normalImage = new BitmapDrawable(getResources(), bitmap);
updateImage(this.normalImage);
}
public void setCheckedImage(Bitmap bitmap) {
this.checkedImage = new BitmapDrawable(getResources(), bitmap);
}
private void updateBackground(int resId) {
setBackgroundResource(resId);
setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
// int padding = ScreenUtil.dip2px(7);
// setPadding(padding, padding, padding, padding);
}
private void updateImage(Drawable drawable) {
// setScaleType(ScaleType.FIT_CENTER);
setImageDrawable(drawable);
}
}

View File

@@ -0,0 +1,287 @@
package com.fengliyan.uikit.emoji.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.viewpager.widget.ViewPager;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.emoji.IEmoticonCategoryChanged;
import com.fengliyan.uikit.emoji.IEmoticonSelectedListener;
import com.fengliyan.uikit.emoji.StickerCategory;
import com.fengliyan.uikit.emoji.manager.StickerManager;
import com.fengliyan.uikit.emoji.utils.BitmapDecoder;
import com.fengliyan.uikit.emoji.utils.ScreenUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 贴图表情选择控件
*/
public class EmoticonPickerView extends LinearLayout implements IEmoticonCategoryChanged {
private Context context;
private IEmoticonSelectedListener listener;
private boolean loaded = false;
private boolean withSticker;
private EmoticonView gifView;
private ViewPager currentEmojiPage;
private LinearLayout pageNumberLayout;//页面布局
private HorizontalScrollView scrollView;
private LinearLayout tabView;
private int categoryIndex;
private Handler uiHandler;
public EmoticonPickerView(Context context) {
super(context);
init(context);
}
public EmoticonPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public EmoticonPickerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
this.context = context;
this.uiHandler = new Handler(context.getMainLooper());
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.nim_emoji_layout, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setupEmojView();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
public void show(IEmoticonSelectedListener listener) {
setListener(listener);
if (loaded)
return;
loadStickers();
loaded = true;
show();
}
public void setListener(IEmoticonSelectedListener listener) {
if (listener != null) {
this.listener = listener;
} else {
}
}
protected void setupEmojView() {
currentEmojiPage = (ViewPager) findViewById(R.id.scrPlugin);
pageNumberLayout = (LinearLayout) findViewById(R.id.layout_scr_bottom);
tabView = (LinearLayout) findViewById(R.id.emoj_tab_view);
scrollView = (HorizontalScrollView) findViewById(R.id.emoj_tab_view_container);
findViewById(R.id.top_divider_line).setVisibility(View.VISIBLE);
}
// 添加各个tab按钮
OnClickListener tabCheckListener = new OnClickListener() {
@Override
public void onClick(View v) {
onEmoticonBtnChecked(v.getId());
}
};
private void loadStickers() {
if (!withSticker) {
scrollView.setVisibility(View.GONE);
return;
}
final StickerManager manager = StickerManager.getInstance();
tabView.removeAllViews();
int index = 0;
// emoji表情
CheckedImageButton btn = addEmoticonTabBtn(index++, tabCheckListener);
btn.setNormalImageId(R.drawable.nim_emoji_icon_inactive);
btn.setCheckedImageId(R.drawable.nim_emoji_icon);
// 贴图
List<StickerCategory> categories = manager.getCategories();
for (StickerCategory category : categories) {
btn = addEmoticonTabBtn(index++, tabCheckListener);
setCheckedButtomImage(btn, category);
}
}
private CheckedImageButton addEmoticonTabBtn(int index, OnClickListener listener) {
CheckedImageButton emotBtn = new CheckedImageButton(context);
emotBtn.setNormalBkResId(R.drawable.nim_sticker_button_background_normal_layer_list);
emotBtn.setCheckedBkResId(R.drawable.nim_sticker_button_background_pressed_layer_list);
emotBtn.setId(index);
emotBtn.setOnClickListener(listener);
emotBtn.setScaleType(ImageView.ScaleType.FIT_CENTER);
emotBtn.setPaddingValue(ScreenUtil.dip2px(7));
final int emojiBtnWidth = ScreenUtil.dip2px(50);
final int emojiBtnHeight = ScreenUtil.dip2px(44);
tabView.addView(emotBtn);
ViewGroup.LayoutParams emojBtnLayoutParams = emotBtn.getLayoutParams();
emojBtnLayoutParams.width = emojiBtnWidth;
emojBtnLayoutParams.height = emojiBtnHeight;
emotBtn.setLayoutParams(emojBtnLayoutParams);
return emotBtn;
}
private void setCheckedButtomImage(CheckedImageButton btn, StickerCategory category) {
try {
InputStream is = category.getCoverNormalInputStream(context);
if (is != null) {
Bitmap bmp = BitmapDecoder.decode(is);
btn.setNormalImage(bmp);
is.close();
}
is = category.getCoverPressedInputStream(context);
if (is != null) {
Bitmap bmp = BitmapDecoder.decode(is);
btn.setCheckedImage(bmp);
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void onEmoticonBtnChecked(int index) {
updateTabButton(index);
showEmotPager(index);
}
private void updateTabButton(int index) {
for (int i = 0; i < tabView.getChildCount(); ++i) {
View child = tabView.getChildAt(i);
if (child instanceof FrameLayout) {
child = ((FrameLayout) child).getChildAt(0);
}
if (child != null && child instanceof CheckedImageButton) {
CheckedImageButton tabButton = (CheckedImageButton) child;
if (tabButton.isChecked() && i != index) {
tabButton.setChecked(false);
} else if (!tabButton.isChecked() && i == index) {
tabButton.setChecked(true);
}
}
}
}
private void showEmotPager(int index) {
if (gifView == null) {
gifView = new EmoticonView(context, listener, currentEmojiPage, pageNumberLayout);
gifView.setCategoryChangCheckedCallback(this);
}
gifView.showStickers(index);
}
private void showEmojiView() {
if (gifView == null) {
gifView = new EmoticonView(context, listener, currentEmojiPage, pageNumberLayout);
}
gifView.showEmojis();
}
private void show() {
if (listener == null) {
}
if (!withSticker) {
showEmojiView();
} else {
onEmoticonBtnChecked(0);
setSelectedVisible(0);
}
}
private void setSelectedVisible(final int index) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (scrollView.getChildAt(0).getWidth() == 0) {
uiHandler.postDelayed(this, 100);
}
int x = -1;
View child = tabView.getChildAt(index);
if (child != null) {
if (child.getRight() > scrollView.getWidth()) {
x = child.getRight() - scrollView.getWidth();
}
}
if (x != -1) {
scrollView.smoothScrollTo(x, 0);
}
}
};
uiHandler.postDelayed(runnable, 100);
}
@Override
public void onCategoryChanged(int index) {
if (categoryIndex == index) {
return;
}
categoryIndex = index;
updateTabButton(index);
}
public void setWithSticker(boolean withSticker) {
this.withSticker = withSticker;
}
}

View File

@@ -0,0 +1,395 @@
package com.fengliyan.uikit.emoji.view;
import android.content.Context;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.emoji.IEmoticonCategoryChanged;
import com.fengliyan.uikit.emoji.IEmoticonSelectedListener;
import com.fengliyan.uikit.emoji.StickerCategory;
import com.fengliyan.uikit.emoji.StickerItem;
import com.fengliyan.uikit.emoji.manager.EmojiAdapter;
import com.fengliyan.uikit.emoji.manager.EmojiManager;
import com.fengliyan.uikit.emoji.manager.StickerAdapter;
import com.fengliyan.uikit.emoji.manager.StickerManager;
import java.util.ArrayList;
import java.util.List;
/**
* 贴图显示viewpager
*/
public class EmoticonView {
private ViewPager emotPager;
private LinearLayout pageNumberLayout;
/**
* 总页数.
*/
private int pageCount;
/**
* 每页显示的数量Adapter保持一致.
*/
public static final int EMOJI_PER_PAGE = 27; // 最后一个是删除键
public static final int STICKER_PER_PAGE = 8;
private Context context;
private IEmoticonSelectedListener listener;
private EmoticonViewPaperAdapter pagerAdapter = new EmoticonViewPaperAdapter();
/**
* 所有表情贴图支持横向滑动切换
*/
private int categoryIndex; // 当套贴图的在picker中的索引
private boolean isDataInitialized = false; // 数据源只需要初始化一次,变更时再初始化
private List<StickerCategory> categoryDataList; // 表情贴图数据源
private List<Integer> categoryPageNumberList; // 每套表情贴图对应的页数
private int[] pagerIndexInfo = new int[2]; // 0category index1pager index in category
private IEmoticonCategoryChanged categoryChangedCallback; // 横向滑动切换时回调picker
public EmoticonView(Context context, IEmoticonSelectedListener mlistener,
ViewPager mCurPage, LinearLayout pageNumberLayout) {
this.context = context.getApplicationContext();
this.listener = mlistener;
this.pageNumberLayout = pageNumberLayout;
this.emotPager = mCurPage;
emotPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
if (categoryDataList != null) {
// 显示所有贴图表情
setCurStickerPage(position);
if (categoryChangedCallback != null) {
int currentCategoryChecked = pagerIndexInfo[0];// 当前那种类别被选中
categoryChangedCallback.onCategoryChanged(currentCategoryChecked);
}
} else {
// 只显示表情
setCurEmotionPage(position);
}
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
emotPager.setAdapter(pagerAdapter);
emotPager.setOffscreenPageLimit(1);
}
public void setCategoryDataReloadFlag() {
isDataInitialized = false;
}
public void showStickers(int index) {
// 判断是否需要变化
if (isDataInitialized && getPagerInfo(emotPager.getCurrentItem()) != null
&& pagerIndexInfo[0] == index && pagerIndexInfo[1] == 0) {
return;
}
this.categoryIndex = index;
showStickerGridView();
}
public void showEmojis() {
showEmojiGridView();
}
private int getCategoryPageCount(StickerCategory category) {
if (category == null) {
return (int) Math.ceil(EmojiManager.getDisplayCount() / (float) EMOJI_PER_PAGE);
} else {
if (category.hasStickers()) {
List<StickerItem> stickers = category.getStickers();
return (int) Math.ceil(stickers.size() / (float) STICKER_PER_PAGE);
} else {
return 1;
}
}
}
private void setCurPage(int page, int pageCount) {
int hasCount = pageNumberLayout.getChildCount();
int forMax = Math.max(hasCount, pageCount);
ImageView imgCur = null;
for (int i = 0; i < forMax; i++) {
if (pageCount <= hasCount) {
if (i >= pageCount) {
pageNumberLayout.getChildAt(i).setVisibility(View.GONE);
continue;
} else {
imgCur = (ImageView) pageNumberLayout.getChildAt(i);
}
} else {
if (i < hasCount) {
imgCur = (ImageView) pageNumberLayout.getChildAt(i);
} else {
imgCur = new ImageView(context);
imgCur.setBackgroundResource(R.drawable.nim_view_pager_indicator_selector);
pageNumberLayout.addView(imgCur);
}
}
imgCur.setId(i);
imgCur.setSelected(i == page); // 判断当前页码来更新
imgCur.setVisibility(View.VISIBLE);
}
}
/**
* ******************************** 表情 *******************************
*/
private void showEmojiGridView() {
pageCount = (int) Math.ceil(EmojiManager.getDisplayCount() / (float) EMOJI_PER_PAGE);
pagerAdapter.notifyDataSetChanged();
resetEmotionPager();
}
private void resetEmotionPager() {
setCurEmotionPage(0);
emotPager.setCurrentItem(0, false);
}
private void setCurEmotionPage(int position) {
setCurPage(position, pageCount);
}
public OnItemClickListener emojiListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
int position = emotPager.getCurrentItem();
int pos = position; // 如果只有表情,那么用默认方式计算
if (categoryDataList != null && categoryPageNumberList != null) {
// 包含贴图
getPagerInfo(position);
pos = pagerIndexInfo[1];
}
int index = arg2 + pos * EMOJI_PER_PAGE;
if (listener != null) {
int count = EmojiManager.getDisplayCount();
if (arg2 == EMOJI_PER_PAGE || index >= count) {
listener.onEmojiSelected("/DEL");
} else {
String text = EmojiManager.getDisplayText((int) arg3);
if (!TextUtils.isEmpty(text)) {
listener.onEmojiSelected(text);
}
}
}
}
};
/**
* ******************************** 贴图 *******************************
*/
private void showStickerGridView() {
initData();
pagerAdapter.notifyDataSetChanged();
// 计算起始的pager index
int position = 0;
for (int i = 0; i < categoryPageNumberList.size(); i++) {
if (i == categoryIndex) {
break;
}
position += categoryPageNumberList.get(i);
}
setCurStickerPage(position);
emotPager.setCurrentItem(position, false);
}
private void initData() {
if (isDataInitialized) {//数据已经初始化,未变动不重新加载数据
return;
}
if (categoryDataList == null) {
categoryDataList = new ArrayList<>();
}
if (categoryPageNumberList == null) {
categoryPageNumberList = new ArrayList<>();
}
categoryDataList.clear();
categoryPageNumberList.clear();
final StickerManager manager = StickerManager.getInstance();
categoryDataList.add(null); // 表情
categoryPageNumberList.add(getCategoryPageCount(null));
List<StickerCategory> categories = manager.getCategories();
categoryDataList.addAll(categories); // 贴图
for (StickerCategory c : categories) {
categoryPageNumberList.add(getCategoryPageCount(c));
}
pageCount = 0;//总页数
for (Integer count : categoryPageNumberList) {
pageCount += count;
}
isDataInitialized = true;
}
// 给定pager中的索引返回categoryIndex和positionInCategory
private int[] getPagerInfo(int position) {
if (categoryDataList == null || categoryPageNumberList == null) {
return pagerIndexInfo;
}
int cIndex = categoryIndex;
int startIndex = 0;
int pageNumberPerCategory = 0;
for (int i = 0; i < categoryPageNumberList.size(); i++) {
pageNumberPerCategory = categoryPageNumberList.get(i);
if (position < startIndex + pageNumberPerCategory) {
cIndex = i;
break;
}
startIndex += pageNumberPerCategory;
}
this.pagerIndexInfo[0] = cIndex;
this.pagerIndexInfo[1] = position - startIndex;
return pagerIndexInfo;
}
private void setCurStickerPage(int position) {
getPagerInfo(position);
int categoryIndex = pagerIndexInfo[0];
int pageIndexInCategory = pagerIndexInfo[1];
int categoryPageCount = categoryPageNumberList.get(categoryIndex);
setCurPage(pageIndexInCategory, categoryPageCount);
}
public void setCategoryChangCheckedCallback(IEmoticonCategoryChanged callback) {
this.categoryChangedCallback = callback;
}
private OnItemClickListener stickerListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
int position = emotPager.getCurrentItem();
getPagerInfo(position);
int cIndex = pagerIndexInfo[0];
int pos = pagerIndexInfo[1];
StickerCategory c = categoryDataList.get(cIndex);
int index = arg2 + pos * STICKER_PER_PAGE; // 在category中贴图的index
if (index >= c.getStickers().size()) {
return;
}
if (listener != null) {
StickerManager manager = StickerManager.getInstance();
List<StickerItem> stickers = c.getStickers();
StickerItem sticker = stickers.get(index);
StickerCategory real = manager.getCategory(sticker.getCategory());
if (real == null) {
return;
}
listener.onStickerSelected(sticker.getCategory(), sticker.getName());
}
}
};
/**
* ***************************** PagerAdapter ****************************
*/
private class EmoticonViewPaperAdapter extends PagerAdapter {
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getCount() {
return pageCount == 0 ? 1 : pageCount;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
StickerCategory category;
int pos;
if (categoryDataList != null && categoryDataList.size() > 0 && categoryPageNumberList != null
&& categoryPageNumberList.size() > 0) {
// 显示所有贴图&表情
getPagerInfo(position);
int cIndex = pagerIndexInfo[0];
category = categoryDataList.get(cIndex);
pos = pagerIndexInfo[1];
} else {
// 只显示表情
category = null;
pos = position;
}
if (category == null) {
pageNumberLayout.setVisibility(View.VISIBLE);
GridView gridView = new GridView(context);
gridView.setOnItemClickListener(emojiListener);
gridView.setAdapter(new EmojiAdapter(context, pos * EMOJI_PER_PAGE));
gridView.setNumColumns(7);
gridView.setHorizontalSpacing(5);
gridView.setVerticalSpacing(5);
gridView.setGravity(Gravity.CENTER);
gridView.setSelector(R.drawable.nim_emoji_item_selector);
container.addView(gridView);
return gridView;
} else {
pageNumberLayout.setVisibility(View.VISIBLE);
GridView gridView = new GridView(context);
gridView.setPadding(10, 0, 10, 0);
gridView.setOnItemClickListener(stickerListener);
gridView.setAdapter(new StickerAdapter(context, category, pos * STICKER_PER_PAGE));
gridView.setNumColumns(4);
gridView.setHorizontalSpacing(5);
gridView.setGravity(Gravity.CENTER);
gridView.setSelector(R.drawable.nim_emoji_item_selector);
container.addView(gridView);
return gridView;
}
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View layout = (View) object;
container.removeView(layout);
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
}

View File

@@ -0,0 +1,56 @@
package com.fengliyan.uikit.flingswipe;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.AdapterView;
/**
* Created by dionysis_lorentzos on 6/8/14
* for package com.lorentzos.swipecards
* and project Swipe cards.
* Use with caution dinausaurs might appear!
*/
abstract class BaseFlingAdapterView extends AdapterView {
private int heightMeasureSpec;
private int widthMeasureSpec;
public BaseFlingAdapterView(Context context) {
super(context);
}
public BaseFlingAdapterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BaseFlingAdapterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setSelection(int i) {
throw new UnsupportedOperationException("Not supported");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.widthMeasureSpec = widthMeasureSpec;
this.heightMeasureSpec = heightMeasureSpec;
}
public int getWidthMeasureSpec() {
return widthMeasureSpec;
}
public int getHeightMeasureSpec() {
return heightMeasureSpec;
}
}

View File

@@ -0,0 +1,379 @@
package com.fengliyan.uikit.flingswipe;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.graphics.PointF;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.OvershootInterpolator;
/**
* Created by dionysis_lorentzos on 5/8/14
* for package com.lorentzos.swipecards
* and project Swipe cards.
* Use with caution dinausaurs might appear!
*/
public class FlingCardListener implements View.OnTouchListener {
private static final String TAG = FlingCardListener.class.getSimpleName();
private static final int INVALID_POINTER_ID = -1;
private final float objectX;
private final float objectY;
private final int objectH;
private final int objectW;
private final int parentWidth;
private final FlingListener mFlingListener;
private final Object dataObject;
private final float halfWidth;
private float BASE_ROTATION_DEGREES;
private float aPosX;
private float aPosY;
private float aDownTouchX;
private float aDownTouchY;
// The active pointer is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
private View frame = null;
private final int TOUCH_ABOVE = 0;
private final int TOUCH_BELOW = 1;
private int touchPosition;
private final Object obj = new Object();
private boolean isAnimationRunning = false;
private float MAX_COS = (float) Math.cos(Math.toRadians(45));
public FlingCardListener(View frame, Object itemAtPosition, FlingListener flingListener) {
this(frame, itemAtPosition, 15f, flingListener);
}
public FlingCardListener(View frame, Object itemAtPosition, float rotation_degrees, FlingListener flingListener) {
super();
this.frame = frame;
this.objectX = frame.getX();
this.objectY = frame.getY();
this.objectH = frame.getHeight();
this.objectW = frame.getWidth();
this.halfWidth = objectW / 2f;
this.dataObject = itemAtPosition;
this.parentWidth = ((ViewGroup) frame.getParent()).getWidth();
this.BASE_ROTATION_DEGREES = rotation_degrees;
this.mFlingListener = flingListener;
}
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// from http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
// Save the ID of this pointer
mActivePointerId = event.getPointerId(0);
float x = 0;
float y = 0;
boolean success = false;
try {
x = event.getX(mActivePointerId);
y = event.getY(mActivePointerId);
success = true;
} catch (IllegalArgumentException e) {
Log.w(TAG, "Exception in onTouch(view, event) : " + mActivePointerId, e);
}
if (success) {
// Remember where we started
aDownTouchX = x;
aDownTouchY = y;
//to prevent an initial jump of the magnifier, aposX and aPosY must
//have the values from the magnifier frame
if (aPosX == 0) {
aPosX = frame.getX();
}
if (aPosY == 0) {
aPosY = frame.getY();
}
if (y < objectH / 2) {
touchPosition = TOUCH_ABOVE;
} else {
touchPosition = TOUCH_BELOW;
}
}
view.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER_ID;
resetCardViewOnStack();
view.getParent().requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_POINTER_UP:
// Extract the index of the pointer that left the touch sensor
final int pointerIndex = (event.getAction() &
MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
case MotionEvent.ACTION_MOVE:
// Log.e("move", "move......");
// Find the index of the active pointer and fetch its position
final int pointerIndexMove = event.findPointerIndex(mActivePointerId);
final float xMove = event.getX(pointerIndexMove);
final float yMove = event.getY(pointerIndexMove);
//from http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
// Calculate the distance moved
final float dx = xMove - aDownTouchX;
final float dy = yMove - aDownTouchY;
// Move the frame
aPosX += dx;
aPosY += dy;
// Log.e("x,y", aPosX + "," + aPosY);
// calculate the rotation degrees
float distobjectX = aPosX - objectX;
float rotation = BASE_ROTATION_DEGREES * 2.f * distobjectX / parentWidth;
if (touchPosition == TOUCH_BELOW) {
rotation = -rotation;
}
//in this area would be code for doing something with the view as the frame moves.
frame.setX(aPosX);
frame.setY(aPosY);
// frame.setRotation(rotation);
mFlingListener.onMoveXY(aPosX, aPosY);
mFlingListener.onScroll(getScrollProgressPercent());
break;
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
view.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
}
return true;
}
private float getScrollProgressPercent() {
if (movedBeyondLeftBorder()) {
return -1f;
} else if (movedBeyondRightBorder()) {
return 1f;
} else {
float zeroToOneValue = (aPosX + halfWidth - leftBorder()) / (rightBorder() - leftBorder());
return zeroToOneValue * 2f - 1f;
}
}
private boolean resetCardViewOnStack() {
if (movedBeyondLeftBorder()) {
// Left Swipe
onSelected(true, getExitPoint(-objectW), 100);
mFlingListener.onScroll(-1.0f);
mFlingListener.onMoveXY(0, 0);
} else if (movedBeyondRightBorder()) {
// Right Swipe
onSelected(false, getExitPoint(parentWidth), 100);
mFlingListener.onScroll(1.0f);
mFlingListener.onMoveXY(0, 0);
} else {
mFlingListener.onMoveXY(0, 0);
float abslMoveDistance = Math.abs(aPosX - objectX);
//距离不够回到起点
aPosX = 0;
aPosY = 0;
aDownTouchX = 0;
aDownTouchY = 0;
Log.e("v",frame.getX()+"");
frame.animate()
.setDuration(200)
.setInterpolator(new OvershootInterpolator(1.5f))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
Log.e("v",frame.getX()+"");
Log.e("v",frame.getX()+"");
Log.e("v",frame.getX()+"");
}
@Override
public void onAnimationPause(Animator animation) {
super.onAnimationPause(animation);
Log.e("v",frame.getX()+"");
}
})
.x(objectX)
.y(objectY)
.rotation(0);
mFlingListener.onScroll(0.0f);
if (abslMoveDistance < 4.0) {
mFlingListener.onClick(dataObject);
}
}
return false;
}
private boolean movedBeyondLeftBorder() {
return aPosX + halfWidth < leftBorder();
}
private boolean movedBeyondRightBorder() {
return aPosX + halfWidth > rightBorder();
}
public float leftBorder() {
return parentWidth / 5.f;
}
public float rightBorder() {
return 3 * parentWidth / 5.f;
}
public void onSelected(final boolean isLeft,
float exitY, long duration) {
isAnimationRunning = true;
float exitX;
if (isLeft) {
exitX = -objectW - getRotationWidthOffset();
} else {
exitX = parentWidth + getRotationWidthOffset();
}
this.frame.animate()
.setDuration(duration)
.setInterpolator(new AccelerateInterpolator())
.x(exitX)
.y(exitY)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (isLeft) {
mFlingListener.onCardExited();
mFlingListener.leftExit(dataObject);
} else {
mFlingListener.onCardExited();
mFlingListener.rightExit(dataObject);
}
isAnimationRunning = false;
}
})
.rotation(getExitRotation(isLeft));
}
/**
* Starts a default left exit animation.
*/
public void selectLeft() {
if (!isAnimationRunning)
onSelected(true, objectY, 200);
}
/**
* Starts a default right exit animation.
*/
public void selectRight() {
if (!isAnimationRunning)
onSelected(false, objectY, 200);
}
private float getExitPoint(int exitXPoint) {
float[] x = new float[2];
x[0] = objectX;
x[1] = aPosX;
float[] y = new float[2];
y[0] = objectY;
y[1] = aPosY;
LinearRegression regression = new LinearRegression(x, y);
//Your typical y = ax+b linear regression
return (float) regression.slope() * exitXPoint + (float) regression.intercept();
}
private float getExitRotation(boolean isLeft) {
float rotation = BASE_ROTATION_DEGREES * 2.f * (parentWidth - objectX) / parentWidth;
if (touchPosition == TOUCH_BELOW) {
rotation = -rotation;
}
if (isLeft) {
rotation = -rotation;
}
return rotation;
}
/**
* When the object rotates it's width becomes bigger.
* The maximum width is at 45 degrees.
* <p/>
* The below method calculates the width offset of the rotation.
*/
private float getRotationWidthOffset() {
return objectW / MAX_COS - objectW;
}
public void setRotationDegrees(float degrees) {
this.BASE_ROTATION_DEGREES = degrees;
}
public boolean isTouching() {
return this.mActivePointerId != INVALID_POINTER_ID;
}
public PointF getLastPoint() {
return new PointF(this.aPosX, this.aPosY);
}
protected interface FlingListener {
void onCardExited();
void leftExit(Object dataObject);
void rightExit(Object dataObject);
void onClick(Object dataObject);
void onScroll(float scrollProgressPercent);
void onMoveXY(float moveX, float moveY);
}
}

View File

@@ -0,0 +1,143 @@
package com.fengliyan.uikit.flingswipe;
/*************************************************************************
* Compilation: javac LinearRegression.java
* Execution: java LinearRegression
*
* Compute least squares solution to y = beta * x + alpha.
* Simple linear regression.
*
*************************************************************************/
/**
* The <tt>LinearRegression</tt> class performs a simple linear regression
* on an set of <em>N</em> data points (<em>y<sub>i</sub></em>, <em>x<sub>i</sub></em>).
* That is, it fits a straight line <em>y</em> = &alpha; + &beta; <em>x</em>,
* (where <em>y</em> is the response variable, <em>x</em> is the predictor variable,
* &alpha; is the <em>y-intercept</em>, and &beta; is the <em>slope</em>)
* that minimizes the sum of squared residuals of the linear regression model.
* It also computes associated statistics, including the coefficient of
* determination <em>R</em><sup>2</sup> and the standard deviation of the
* estimates for the slope and <em>y</em>-intercept.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
class LinearRegression {
private final int N;
private final double alpha, beta;
private final double R2;
private final double svar, svar0, svar1;
/**
* Performs a linear regression on the data points <tt>(y[i], x[i])</tt>.
* @param x the values of the predictor variable
* @param y the corresponding values of the response variable
* @throws IllegalArgumentException if the lengths of the two arrays are not equal
*/
public LinearRegression(float[] x, float[] y) {
if (x.length != y.length) {
throw new IllegalArgumentException("array lengths are not equal");
}
N = x.length;
// first pass
double sumx = 0.0, sumy = 0.0, sumx2 = 0.0;
for (int i = 0; i < N; i++) sumx += x[i];
for (int i = 0; i < N; i++) sumx2 += x[i]*x[i];
for (int i = 0; i < N; i++) sumy += y[i];
double xbar = sumx / N;
double ybar = sumy / N;
// second pass: compute summary statistics
double xxbar = 0.0, yybar = 0.0, xybar = 0.0;
for (int i = 0; i < N; i++) {
xxbar += (x[i] - xbar) * (x[i] - xbar);
yybar += (y[i] - ybar) * (y[i] - ybar);
xybar += (x[i] - xbar) * (y[i] - ybar);
}
beta = xybar / xxbar;
alpha = ybar - beta * xbar;
// more statistical analysis
double rss = 0.0; // residual sum of squares
double ssr = 0.0; // regression sum of squares
for (int i = 0; i < N; i++) {
double fit = beta*x[i] + alpha;
rss += (fit - y[i]) * (fit - y[i]);
ssr += (fit - ybar) * (fit - ybar);
}
int degreesOfFreedom = N-2;
R2 = ssr / yybar;
svar = rss / degreesOfFreedom;
svar1 = svar / xxbar;
svar0 = svar/N + xbar*xbar*svar1;
}
/**
* Returns the <em>y</em>-intercept &alpha; of the best of the best-fit line <em>y</em> = &alpha; + &beta; <em>x</em>.
* @return the <em>y</em>-intercept &alpha; of the best-fit line <em>y = &alpha; + &beta; x</em>
*/
public double intercept() {
return alpha;
}
/**
* Returns the slope &beta; of the best of the best-fit line <em>y</em> = &alpha; + &beta; <em>x</em>.
* @return the slope &beta; of the best-fit line <em>y</em> = &alpha; + &beta; <em>x</em>
*/
public double slope() {
return beta;
}
/**
* Returns the coefficient of determination <em>R</em><sup>2</sup>.
* @return the coefficient of determination <em>R</em><sup>2</sup>, which is a real number between 0 and 1
*/
public double R2() {
return R2;
}
/**
* Returns the standard error of the estimate for the intercept.
* @return the standard error of the estimate for the intercept
*/
public double interceptStdErr() {
return Math.sqrt(svar0);
}
/**
* Returns the standard error of the estimate for the slope.
* @return the standard error of the estimate for the slope
*/
public double slopeStdErr() {
return Math.sqrt(svar1);
}
/**
* Returns the expected response <tt>y</tt> given the value of the predictor
* variable <tt>x</tt>.
* @param x the value of the predictor variable
* @return the expected response <tt>y</tt> given the value of the predictor
* variable <tt>x</tt>
*/
public double predict(double x) {
return beta*x + alpha;
}
/**
* Returns a string representation of the simple linear regression model.
* @return a string representation of the simple linear regression model,
* including the best-fit line and the coefficient of determination <em>R</em><sup>2</sup>
*/
public String toString() {
String s = "";
s += String.format("%.2f N + %.2f", slope(), intercept());
return s + " (R^2 = " + String.format("%.3f", R2()) + ")";
}
}

View File

@@ -0,0 +1,387 @@
package com.fengliyan.uikit.flingswipe;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.PointF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.Adapter;
import android.widget.FrameLayout;
import com.fengliyan.uikit.R;
/**
* Created by dionysis_lorentzos on 5/8/14
* for package com.lorentzos.swipecards
* and project Swipe cards.
* Use with caution dinosaurs might appear!
*/
public class SwipeFlingAdapterView extends BaseFlingAdapterView {
private int MAX_VISIBLE = 4;
private int MIN_ADAPTER_STACK = 6;
private float ROTATION_DEGREES = 15.f;
private float ITEM_SMALL_WIDTH = 20;
private float ITEM_SMALL_HIGH = 32;
private Adapter mAdapter;
private int LAST_OBJECT_IN_STACK = 0;
private onFlingListener mFlingListener;
private AdapterDataSetObserver mDataSetObserver;
private boolean mInLayout = false;
private View mActiveCard = null;
private OnItemClickListener mOnItemClickListener;
private FlingCardListener flingCardListener;
private PointF mLastTouchPoint;
private float p = 0f;
public SwipeFlingAdapterView(Context context) {
this(context, null);
}
public SwipeFlingAdapterView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.SwipeFlingStyle);
}
public SwipeFlingAdapterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeFlingAdapterView, defStyle, 0);
MAX_VISIBLE = a.getInt(R.styleable.SwipeFlingAdapterView_max_visible, MAX_VISIBLE);
MIN_ADAPTER_STACK = a.getInt(R.styleable.SwipeFlingAdapterView_min_adapter_stack, MIN_ADAPTER_STACK);
ROTATION_DEGREES = a.getFloat(R.styleable.SwipeFlingAdapterView_rotation_degrees, ROTATION_DEGREES);
a.recycle();
}
private float dpToPx(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sp, getContext().getResources().getDisplayMetrics());
}
/**
* A shortcut method to set both the listeners and the adapter.
*
* @param context The activity context which extends onFlingListener, OnItemClickListener or both
* @param mAdapter The adapter you have to set.
*/
public void init(final Context context, Adapter mAdapter) {
if (context instanceof onFlingListener) {
mFlingListener = (onFlingListener) context;
} else {
throw new RuntimeException("Activity does not implement SwipeFlingAdapterView.onFlingListener");
}
if (context instanceof OnItemClickListener) {
mOnItemClickListener = (OnItemClickListener) context;
}
setAdapter(mAdapter);
}
@Override
public View getSelectedView() {
return mActiveCard;
}
@Override
public void requestLayout() {
if (!mInLayout) {
super.requestLayout();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// if we don't have an adapter, we don't need to do anything
if (mAdapter == null) {
return;
}
mInLayout = true;
final int adapterCount = mAdapter.getCount();
if (adapterCount == 0) {
removeAllViewsInLayout();
} else {
View topCard = getChildAt(LAST_OBJECT_IN_STACK);
if (mActiveCard != null && topCard != null && topCard == mActiveCard) {
if (this.flingCardListener.isTouching()) {
PointF lastPoint = this.flingCardListener.getLastPoint();
if (this.mLastTouchPoint == null || !this.mLastTouchPoint.equals(lastPoint)) {
this.mLastTouchPoint = lastPoint;
removeViewsInLayout(0, LAST_OBJECT_IN_STACK);
layoutChildren(1, adapterCount, 3);
}
}
} else {
// Reset the UI and set top view listener
removeAllViewsInLayout();
layoutChildren(0, adapterCount, 3);
setTopView();
}
}
mInLayout = false;
if (adapterCount <= MIN_ADAPTER_STACK) mFlingListener.onAdapterAboutToEmpty(adapterCount);
}
private void layoutChildren(int startingIndex, int adapterCount, int count) {
while (startingIndex < Math.min(adapterCount, 4)) {
View newUnderChild = mAdapter.getView(startingIndex, null, this);
if (newUnderChild.getVisibility() != GONE) {
makeAndAddView(startingIndex, newUnderChild);
LAST_OBJECT_IN_STACK = startingIndex;
}
startingIndex++;
}
}
/**
* 跳转改变view 大小
*
* @param child
* @param index
*/
private void adjustChildView(View child, int index) {
int n;
if (index > 1)
n = 2;
else
n = index;
if (index == 3 && p > 0.5f) {
n = index;
}
child.offsetTopAndBottom((int) (dpToPx((int) ITEM_SMALL_HIGH) * (n - p)));
child.setScaleX(1 - 0.1f * (n - p));
child.setScaleY(1 - 0.1f * (n - p));
}
/**
* 绘制子View
*
* @param index
* @param child
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private void makeAndAddView(int index, View child) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
addViewInLayout(child, 0, lp, true);
final boolean needToMeasure = child.isLayoutRequested();
if (needToMeasure) {
int childWidthSpec = getChildMeasureSpec(getWidthMeasureSpec(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width);
int childHeightSpec = getChildMeasureSpec(getHeightMeasureSpec(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, lp.height);
child.measure(childWidthSpec, childHeightSpec);
} else {
cleanupLayoutState(child);
}
int w = child.getMeasuredWidth();
int h = child.getMeasuredHeight();
int gravity = lp.gravity;
if (gravity == -1) {
gravity = Gravity.TOP | Gravity.START;
}
int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
int childLeft;
int childTop;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = (getWidth() + getPaddingLeft() - getPaddingRight() - w) / 2 + lp.leftMargin - lp.rightMargin;
break;
case Gravity.END:
childLeft = getWidth() + getPaddingRight() - w - lp.rightMargin;
break;
case Gravity.START:
default:
int l = 0;
childLeft = getPaddingLeft() + lp.leftMargin + l;
break;
}
switch (verticalGravity) {
case Gravity.CENTER_VERTICAL:
childTop = (getHeight() + getPaddingTop() - getPaddingBottom() - h) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = getHeight() - getPaddingBottom() - h - lp.bottomMargin;
break;
case Gravity.TOP:
default:
int top = 0;
childTop = getPaddingTop() + lp.topMargin + top;
break;
}
child.layout(childLeft, childTop, childLeft + w, childTop + h);
adjustChildView(child, index);
}
/**
* Set the top view and add the fling listener
*/
private void setTopView() {
if (getChildCount() > 0) {
mActiveCard = getChildAt(LAST_OBJECT_IN_STACK);
if (mActiveCard != null) {
flingCardListener = new FlingCardListener(mActiveCard, mAdapter.getItem(0),
ROTATION_DEGREES, new FlingCardListener.FlingListener() {
@Override
public void onCardExited() {
mActiveCard = null;
p = 0f;
mFlingListener.removeFirstObjectInAdapter();
}
@Override
public void leftExit(Object dataObject) {
p = 0f;
mFlingListener.onLeftCardExit(dataObject);
}
@Override
public void rightExit(Object dataObject) {
mFlingListener.onRightCardExit(dataObject);
}
@Override
public void onClick(Object dataObject) {
if (mOnItemClickListener != null)
mOnItemClickListener.onItemClicked(0, dataObject);
}
@Override
public void onScroll(float scrollProgressPercent) {
mFlingListener.onScroll(scrollProgressPercent);
}
@Override
public void onMoveXY(float moveX, float moveY) {
float mX = (int) Math.abs(moveX);
float mY = (int) Math.abs(moveY);
if (mX > 50 || mY > 50) {
float m = Math.max(mX, mY);
p = (m - 50f) / 500f;
if (p > 1f) {
p = 1f;
}
} else {
p = 0f;
}
requestLayout();
}
});
mActiveCard.setOnTouchListener(flingCardListener);
}
}
}
public FlingCardListener getTopCardListener() throws NullPointerException {
if (flingCardListener == null) {
throw new NullPointerException();
}
return flingCardListener;
}
public void setMaxVisible(int MAX_VISIBLE) {
this.MAX_VISIBLE = MAX_VISIBLE;
}
public void setMinStackInAdapter(int MIN_ADAPTER_STACK) {
this.MIN_ADAPTER_STACK = MIN_ADAPTER_STACK;
}
@Override
public Adapter getAdapter() {
return mAdapter;
}
@Override
public void setAdapter(Adapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
mDataSetObserver = null;
}
mAdapter = adapter;
if (mAdapter != null && mDataSetObserver == null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
}
}
public void setFlingListener(onFlingListener onFlingListener) {
this.mFlingListener = onFlingListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FrameLayout.LayoutParams(getContext(), attrs);
}
private class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
requestLayout();
}
@Override
public void onInvalidated() {
requestLayout();
}
}
public interface OnItemClickListener {
void onItemClicked(int itemPosition, Object dataObject);
}
public interface onFlingListener {
void removeFirstObjectInAdapter();
void onLeftCardExit(Object dataObject);
void onRightCardExit(Object dataObject);
void onAdapterAboutToEmpty(int itemsInAdapter);
void onScroll(float scrollProgressPercent);
}
}

View File

@@ -0,0 +1,930 @@
package com.fengliyan.uikit.mzbanner;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
public class CoverFlowView extends RelativeLayout {
public enum CoverFlowGravity {
TOP, BOTTOM, CENTER_VERTICAL
}
public enum CoverFlowLayoutMode {
MATCH_PARENT, WRAP_CONTENT
}
protected CoverFlowGravity mGravity;
protected CoverFlowLayoutMode mLayoutMode;
private Scroller mScroller;
/**
* To store reflections need to remove
*/
private ArrayList<View> removeViewArray;
private SparseArray<View> showViewArray;
private int paddingLeft;
private int paddingRight;
private int paddingTop;
private int paddingBottom;
private int mWidth; // 控件的宽度
private float reflectHeightFraction;
private int reflectGap;
private int mChildHeight; // child的高度
private int mChildTranslateY;
//private int mReflectionTranslateY;
private int mVisibleChildCount; // 一屏显示的图片数量
protected int VISIBLE_VIEWS = 3; // the visible views left and right 左右两边显示的个数
private ICoverFlowAdapter mAdapter;
private float mOffset;
//private int mLastOffset;
private final int ALPHA_DATUM = 76; // 基础alphaֵ
private int STANDARD_ALPHA;
// 基础缩放值
//private static final float CARD_SCALE = 0.15f;
private static float MOVE_POS_MULTIPLE = 3.0f;
private static final int TOUCH_MINIMUM_MOVE = 5;
private static final float MOVE_SPEED_MULTIPLE = 1;
private static final float MAX_SPEED = 6.0f;
private static final float FRICTION = 10.0f;
private VelocityTracker mVelocity;
private int firstIndex = 0;
public CoverFlowView(Context context) {
super(context);
init();
}
public CoverFlowView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttributes(context, attrs);
init();
}
public CoverFlowView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttributes(context, attrs);
init();
}
private void initAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageCoverFlowView);
int totalVisibleChildren = a.getInt(
R.styleable.ImageCoverFlowView_visibleImage, 3);
if (totalVisibleChildren % 2 == 0) { // 一屏幕必须是奇数显示
throw new IllegalArgumentException("visible image must be an odd number");
}
VISIBLE_VIEWS = totalVisibleChildren >> 1; // 计算出左右两两边的显示个数
reflectHeightFraction = a.getFraction(
R.styleable.ImageCoverFlowView_reflectionHeight, 100, 0, 0.0f);
if (reflectHeightFraction > 100) {
reflectHeightFraction = 100;
}
reflectHeightFraction /= 100;
reflectGap = a.getDimensionPixelSize(
R.styleable.ImageCoverFlowView_reflectionGap, 0);
mGravity = CoverFlowGravity.values()[a.getInt(
R.styleable.ImageCoverFlowView_coverflowGravity,
CoverFlowGravity.CENTER_VERTICAL.ordinal())];
mLayoutMode = CoverFlowLayoutMode.values()[a.getInt(
R.styleable.ImageCoverFlowView_coverflowLayoutMode,
CoverFlowLayoutMode.WRAP_CONTENT.ordinal())];
a.recycle();
}
private void init() {
removeAllViews();
setWillNotDraw(false);
setClickable(true);
if (mScroller == null) {
mScroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());
}
if (showViewArray == null) {
showViewArray = new SparseArray<View>();
} else {
showViewArray.clear();
}
if (removeViewArray == null) {
removeViewArray = new ArrayList<View>();
} else {
removeViewArray.clear();
}
firstIndex = 0;
mChildHeight = 0;
mOffset = 0;
//mLastOffset = -1;
isFirstIn = true;
lastMid = 1;
isChange = true;
// 计算透明度
STANDARD_ALPHA = (255 - ALPHA_DATUM) / VISIBLE_VIEWS;
if (mGravity == null) {
mGravity = CoverFlowGravity.CENTER_VERTICAL;
}
if (mLayoutMode == null) {
mLayoutMode = CoverFlowLayoutMode.WRAP_CONTENT;
}
// 一屏 显示的图片数量
int visibleCount = (VISIBLE_VIEWS << 1) + 1;
for (int i = 0; i < visibleCount && mAdapter != null && i < mAdapter.getCount(); ++i) {
View convertView = null;
if (removeViewArray.size() > 0) {
convertView = removeViewArray.remove(0);
}
View view = mAdapter.getView(i, convertView, this);
showViewArray.put(i, view);
addView(view);
}
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mAdapter == null || showViewArray.size() <= 0) {
return;
}
paddingLeft = getPaddingLeft();
paddingRight = getPaddingRight();
paddingTop = getPaddingTop();
paddingBottom = getPaddingBottom();
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 一屏显示的图片数量
int visibleCount = (VISIBLE_VIEWS << 1) + 1;
// 控件高度
int availableHeight = heightSize - paddingTop - paddingBottom;
int maxChildTotalHeight = 0;
for (int i = 0; i < getChildCount() && i < visibleCount && i < showViewArray.size(); ++i) {
//View view = showViewArray.get(i+firstIndex);
View view = getChildAt(i);
measureChild(view, widthMeasureSpec, heightMeasureSpec);
//final int childHeight = ScreenUtil.dp2px(getContext(), 110);
final int childHeight = view.getMeasuredHeight();
final int childTotalHeight = (int) (childHeight + childHeight
* reflectHeightFraction + reflectGap);
// 孩子的最大高度
maxChildTotalHeight = (maxChildTotalHeight < childTotalHeight) ? childTotalHeight
: maxChildTotalHeight;
}
// 如果控件模式为确切值 或者 最大时
if (heightMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.AT_MOST) {
// if height which parent provided is less than child need, scale
// child height to parent provide
// 如果控件高度小于孩子控件高度,则缩放孩子高度为控件高度
if (availableHeight < maxChildTotalHeight) {
mChildHeight = availableHeight;
} else {
// if larger than, depends on layout mode
// if layout mode is match_parent, scale child height to parent
// provide
// 如果是填充父窗体模式 则将孩子的高度 设为控件高度
if (mLayoutMode == CoverFlowLayoutMode.MATCH_PARENT) {
mChildHeight = availableHeight;
// if layout mode is wrap_content, keep child's original
// height
// 如果是包裹内容 则将孩子的高度设为孩子允许的最大高度
} else if (mLayoutMode == CoverFlowLayoutMode.WRAP_CONTENT) {
mChildHeight = maxChildTotalHeight;
// adjust parent's height
// 计算出控件的高度
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = mChildHeight + paddingTop + paddingBottom;
}
}
}
} else {
// height mode is unspecified
// 如果空间高度 没有明确定义
// 如果孩子的模式为填充父窗体
if (mLayoutMode == CoverFlowLayoutMode.MATCH_PARENT) {
mChildHeight = availableHeight;
// 如果孩子的模式为包裹内容
} else if (mLayoutMode == CoverFlowLayoutMode.WRAP_CONTENT) {
mChildHeight = maxChildTotalHeight;
// 计算出控件的高度
// adjust parent's height
heightSize = mChildHeight + paddingTop + paddingBottom;
}
}
// Adjust movement in y-axis according to gravity
// 计算出孩子的原点 Y坐标
if (mGravity == CoverFlowGravity.CENTER_VERTICAL) {// 竖直居中
mChildTranslateY = (heightSize >> 1) - (mChildHeight >> 1);
} else if (mGravity == CoverFlowGravity.TOP) {// 顶部对齐
mChildTranslateY = paddingTop;
} else if (mGravity == CoverFlowGravity.BOTTOM) {// 底部对齐
mChildTranslateY = heightSize - paddingBottom - mChildHeight;
}
//mReflectionTranslateY = (int) (mChildTranslateY + mChildHeight - mChildHeight
// * reflectHeightFraction);
setMeasuredDimension(widthSize, heightSize);
mVisibleChildCount = visibleCount;
mWidth = widthSize;
}
boolean isFirstIn = true; // 第一次初始化该控件
int lastMid = 1;
boolean isChange = true;
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mAdapter == null || mAdapter.getCount() <= 0 || showViewArray.size() <= 0) {
return;
}
final float offset = mOffset;
int i = 0;
int mid = (int) Math.floor(offset + 0.5);
//右边孩子的数量
int rightChild = (mVisibleChildCount % 2 == 0) ? (mVisibleChildCount >> 1) - 1
: mVisibleChildCount >> 1;
//左边孩子的数量
int leftChild = mVisibleChildCount >> 1;
if (!isFirstIn) {
if (lastMid + 1 == mid) {
int actuallyPositionStart = getActuallyPosition(lastMid - leftChild);
View view = showViewArray.get(actuallyPositionStart);
showViewArray.remove(actuallyPositionStart);
removeViewArray.add(view);
removeView(view);
View convertView = null;
if (removeViewArray.size() > 0) {
convertView = removeViewArray.remove(0);
}
int actuallyPositionEnd = getActuallyPosition(mid + rightChild);
View viewItem = mAdapter.getView(actuallyPositionEnd, convertView, this);
showViewArray.put(actuallyPositionEnd, viewItem);
addView(viewItem);
isChange = true;
} else if (lastMid - 1 == mid) {
int actuallyPositionEnd = getActuallyPosition(lastMid + rightChild);
View view = showViewArray.get(actuallyPositionEnd);
showViewArray.remove(actuallyPositionEnd);
removeViewArray.add(view);
removeView(view);
View convertView = null;
if (removeViewArray.size() > 0) {
convertView = removeViewArray.remove(0);
}
int actuallyPositionStart = getActuallyPosition(mid - leftChild);
View viewItem = mAdapter.getView(actuallyPositionStart, convertView, this);
showViewArray.put(actuallyPositionStart, viewItem);
addView(viewItem, 0);
isChange = true;
}
} else {
isFirstIn = false;
}
lastMid = mid;
// draw the left children
// 计算左边孩子的位置
int startPos = mid - leftChild;
for (i = startPos; i < mid; ++i) {
// 计算左边孩子的位置
View child = layoutLeftChild(i, i - offset);
if (isChange) {
int actuallyPosition = getActuallyPosition(i);
mAdapter.getData(child, actuallyPosition);
}
}
// 计算 右边 和 中间
int endPos = mid + rightChild;
int j = -VISIBLE_VIEWS;
for (i = endPos; i >= mid; --i, j = j + 2) {
// 计算右边和中间孩子的位置
View child = layoutRightChild(i + j, i - offset);
if (isChange) {
int actuallyPosition = getActuallyPosition(i);
mAdapter.getData(child, actuallyPosition);
}
}
isChange = false;
}
private View layoutLeftChild(int position, float offset) {
//获取实际的position
int actuallyPosition = getActuallyPosition(position);
View child = showViewArray.get(actuallyPosition);
if (child != null) {
makeChildTransfromer(child, actuallyPosition, offset);
}
return child;
}
private View layoutRightChild(int position, float offset) {
//获取实际的position
int actuallyPosition = getActuallyPosition(position);
View child = showViewArray.get(actuallyPosition);
if (child != null) {
makeChildTransfromer(child, actuallyPosition, offset);
}
return child;
}
/**
* 对 bitmap 进行伪 3d 变换
*
* @param child
* @param position
* @param offset
*/
private void makeChildTransfromer(View child, int position, float offset) {
//child.layout(0, 0, ScreenUtil.dp2px(getContext(), 200),ScreenUtil.dp2px(getContext(), 110));
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
float scale = 0;
scale = 1 - Math.abs(offset) * 0.25f;
// 延x轴移动的距离应该根据center图片决定
float translateX = 0;
final int originalChildHeight = (int) (mChildHeight - mChildHeight
* reflectHeightFraction - reflectGap);
//final int childTotalHeight = (int) (child.getHeight()
// + child.getHeight() * reflectHeightFraction + reflectGap);
int height = child.getHeight();
int width = child.getWidth();
if (height == 0) {
height = originalChildHeight;
}
if (width == 0) {
width = originalChildHeight;
}
final float originalChildHeightScale = (float) originalChildHeight / height;
final float childHeightScale = originalChildHeightScale * scale;
final int childWidth = (int) (width * childHeightScale);
final int centerChildWidth = (int) (width * originalChildHeightScale);
//final int centerChildWidth = (int) (child.getWidth() * childHeightScale);
int leftSpace = ((mWidth >> 1) - paddingLeft) - (centerChildWidth >> 1);
int rightSpace = (((mWidth >> 1) - paddingRight) - (centerChildWidth >> 1));
//计算出水平方向的x坐标
if (offset <= 0)
translateX = ((float) leftSpace / VISIBLE_VIEWS)
* (VISIBLE_VIEWS + offset) + paddingLeft;
else
translateX = mWidth - ((float) rightSpace / VISIBLE_VIEWS)
* (VISIBLE_VIEWS - offset) - childWidth - paddingRight;
//根据offset 算出透明度
float alpha = 254 - Math.abs(offset) * STANDARD_ALPHA;
child.setAlpha(0);
if (alpha < 0) {
alpha = 0;
} else if (alpha > 254) {
alpha = 254;
}
child.setAlpha(alpha / 254.0f);
float adjustedChildTranslateY = 0;
if (childHeightScale == Float.POSITIVE_INFINITY || childHeightScale == Float.NEGATIVE_INFINITY || Float.isNaN(childHeightScale)) {
Log.i("TAG", "makeChildTransfromer: childHeightScale------->" + childHeightScale);
// removeAllViews();
// invalidate();
init();
} else {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(child, "scaleX", 1.0f, childHeightScale);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(child, "scaleY", 1.0f, childHeightScale);
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(0);
//两个动画同时执行
animSet.playTogether(anim1, anim2);
child.setPivotX(0);
child.setPivotY(height / 2);
//显示的调用invalidate
child.invalidate();
animSet.setTarget(child);
animSet.start();
child.setTranslationX(translateX);
child.setTranslationY(mChildTranslateY + adjustedChildTranslateY);
}
}
/**
* 获取顶部Item position
*
* @return
*/
public int getTopViewPosition() {
return getActuallyPosition(lastMid);
}
/**
* 获取顶部Item View
*
* @return
*/
public View getTopView() {
return showViewArray.get(getActuallyPosition(VISIBLE_VIEWS + lastMid));
}
/**
* Convert draw-index to index in adapter
*
* @param position position to draw
* @return
*/
private int getActuallyPosition(int position) {
int max = mAdapter.getCount();
position += VISIBLE_VIEWS;
while (position < 0 || position >= max) {
if (position < 0) {
position += max;
} else if (position >= max) {
position -= max;
}
}
return position;
}
public void setAdapter(ICoverFlowAdapter adapter) {
mAdapter = adapter;
init();
}
public ICoverFlowAdapter getAdapter() {
return mAdapter;
}
private boolean onTouchMove = false; //是否正在执行触摸移动逻辑
private View touchViewItem = null;
private boolean isOnTopView = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (isOnAnimator) {
return false;
}
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
onTouchMove = true;
if (mScroller.computeScrollOffset()) {
mScroller.abortAnimation();
invalidate();
requestLayout();
}
touchBegan(event);
touchViewItem = getTopView();
isOnTopView = inRangeOfView(touchViewItem, event);
if (isOnTopView) {
sendLongClickAction();
}
return true;
case MotionEvent.ACTION_MOVE:
touchMoved(event);
removeLongClickAction();
touchViewItem = null;
isOnTopView = false;
return true;
case MotionEvent.ACTION_UP:
removeLongClickAction();
if (isOnTopView && touchViewItem == getTopView()
&& inRangeOfView(touchViewItem, event)) {
if (mTopViewClickListener != null) {
mTopViewClickListener.onClick(getTopViewPosition(), getTopView());
}
}
touchViewItem = null;
isOnTopView = false;
touchEnded(event);
return true;
}
return false;
}
private Runnable longClickRunnable = null;
/**
* 发送长点击事件 Runnable
*/
private void sendLongClickAction() {
removeLongClickAction();
longClickRunnable = new Runnable() {
@Override
public void run() {
touchViewItem = null;
isOnTopView = false;
if (mTopViewLongClickListener != null) {
mTopViewLongClickListener.onLongClick(getTopViewPosition(), getTopView());
}
}
};
postDelayed(longClickRunnable, 600);
}
/**
* 移除长点击事件 Runnable
*/
private void removeLongClickAction() {
if (longClickRunnable != null) {
removeCallbacks(longClickRunnable);
longClickRunnable = null;
}
}
private boolean inRangeOfView(View view, MotionEvent ev) {
Rect frame = new Rect();
view.getHitRect(frame);
if (frame.contains((int) ev.getX(), (int) ev.getY())) {
return true;
}
return false;
}
private OnTopViewClickListener mTopViewClickListener;
private OnTopViewLongClickListener mTopViewLongClickListener;
/**
* 设置 TopView 的点击监听
*
* @param topViewClickListener
*/
public void setOnTopViewClickListener(OnTopViewClickListener topViewClickListener) {
this.mTopViewClickListener = topViewClickListener;
}
/**
* 获取 TopView 的点击监听
*
* @return
*/
public OnTopViewClickListener getOnTopViewClickListener() {
return this.mTopViewClickListener;
}
/**
* 设置 TopView 的长点击监听
*
* @param topViewLongClickListener
*/
public void setOnTopViewLongClickListener(OnTopViewLongClickListener topViewLongClickListener) {
this.mTopViewLongClickListener = topViewLongClickListener;
}
/**
* 获取 TopView 的长点击监听
*
* @return
*/
public OnTopViewLongClickListener getOnTopViewLongClickListener() {
return this.mTopViewLongClickListener;
}
public interface OnTopViewClickListener {
void onClick(int position, View itemView);
}
public interface OnTopViewLongClickListener {
void onLongClick(int position, View itemView);
}
private boolean mTouchMoved;
private float mTouchStartPos;
private float mTouchStartX;
private float mTouchStartY;
private long mStartTime;
private float mStartOffset;
private void touchBegan(MotionEvent event) {
endAnimation();
float x = event.getX();
mTouchStartX = x;
mTouchStartY = event.getY();
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartOffset = mOffset;
mTouchMoved = false;
mTouchStartPos = (x / mWidth) * MOVE_POS_MULTIPLE - 5;
mTouchStartPos /= 2;
mVelocity = VelocityTracker.obtain();
mVelocity.addMovement(event);
}
private Runnable mAnimationRunnable;
private void endAnimation() {
if (mAnimationRunnable != null) {
mOffset = (float) Math.floor(mOffset + 0.5);
invalidate();
requestLayout();
removeCallbacks(mAnimationRunnable);
mAnimationRunnable = null;
}
}
private void touchMoved(MotionEvent event) {
float pos = (event.getX() / mWidth) * MOVE_POS_MULTIPLE - 5;
pos /= 2;
if (!mTouchMoved) {
float dx = Math.abs(event.getX() - mTouchStartX);
float dy = Math.abs(event.getY() - mTouchStartY);
if (dx < TOUCH_MINIMUM_MOVE && dy < TOUCH_MINIMUM_MOVE)
return;
mTouchMoved = true;
}
mOffset = mStartOffset + mTouchStartPos - pos;
invalidate();
requestLayout();
mVelocity.addMovement(event);
}
private void touchEnded(MotionEvent event) {
float pos = (event.getX() / mWidth) * MOVE_POS_MULTIPLE - 5;
pos /= 2;
if (mTouchMoved || (mOffset - Math.floor(mOffset)) != 0) {
mStartOffset += mTouchStartPos - pos;
mOffset = mStartOffset;
mVelocity.addMovement(event);
mVelocity.computeCurrentVelocity(1000);
float speed = mVelocity.getXVelocity();
speed = (speed / mWidth) * MOVE_SPEED_MULTIPLE;
if (speed > MAX_SPEED)
speed = MAX_SPEED;
else if (speed < -MAX_SPEED)
speed = -MAX_SPEED;
startAnimation(-speed);
} else {
Log.e(VIEW_LOG_TAG, " touch ==>" + event.getX() + " , " + event.getY());
onTouchMove = false;
}
mVelocity.clear();
mVelocity.recycle();
}
private float mStartSpeed;
private float mDuration;
private void startAnimation(float speed) {
if (mAnimationRunnable != null) {
onTouchMove = false;
return;
}
float delta = speed * speed / (FRICTION * 2);
if (speed < 0)
delta = -delta;
float nearest = mStartOffset + delta;
nearest = (float) Math.floor(nearest + 0.5f);
mStartSpeed = (float) Math.sqrt(Math.abs(nearest - mStartOffset) * FRICTION * 2);
if (nearest < mStartOffset)
mStartSpeed = -mStartSpeed;
mDuration = Math.abs(mStartSpeed / FRICTION);
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mAnimationRunnable = new Runnable() {
@Override
public void run() {
driveAnimation();
}
};
post(mAnimationRunnable);
}
private void driveAnimation() {
float elapsed = (AnimationUtils.currentAnimationTimeMillis() - mStartTime) / 1000.0f;
if (elapsed >= mDuration) {
endAnimation();
onTouchMove = false;
} else {
updateAnimationAtElapsed(elapsed);
post(mAnimationRunnable);
}
}
private void updateAnimationAtElapsed(float elapsed) {
if (elapsed > mDuration)
elapsed = mDuration;
float delta = Math.abs(mStartSpeed) * elapsed - FRICTION * elapsed * elapsed / 2;
if (mStartSpeed < 0)
delta = -delta;
mOffset = mStartOffset + delta;
invalidate();
requestLayout();
}
@Override
public void computeScroll() {
super.computeScroll();
// 算出移动到某一个虚拟点时 mOffset 的值,然后 invalidate 重绘
if (mScroller.computeScrollOffset()) {
final int currX = mScroller.getCurrX();
mOffset = (float) currX / 100;
invalidate();
}
}
private boolean isOnAnimator = false; //是否正在进行点击切换动画
/**
* 翻到前页
*/
public void gotoPrevious() {
doAnimator(-1.0f);
}
/**
* 前进到后一页
*/
public void gotoForward() {
doAnimator(1.0f);
}
public void doAnimator(float target) {
if (isOnAnimator || onTouchMove) { //如果正在执行点击切换动画 或者 正在执行触摸移动
return;
}
isOnAnimator = true;
ValueAnimator animator = ValueAnimator.ofFloat(mOffset, mOffset + target);
animator.setDuration(300).start();
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOffset = (Float) animation.getAnimatedValue();
invalidate();
requestLayout();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
isOnAnimator = false;
}
});
}
Runnable task = new Runnable() {
@Override
public void run() {
gotoForward();
postDelayed(task, delayTime);
}
};
private int delayTime;
public void startAutoPlay(int delayTime) {
this.delayTime = delayTime;
removeCallbacks(task);
postDelayed(task, delayTime);
}
public void stopAutoPlay() {
removeCallbacks(task);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
return true; // 禁止滑动
}
return super.dispatchTouchEvent(ev);
}
}

View File

@@ -0,0 +1,68 @@
package com.fengliyan.uikit.mzbanner;
import android.content.Context;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.Collections;
/**
* Created by zhouwei on 17/8/16.
*/
public class CustomViewPager extends ViewPager {
private ArrayList<Integer> childCenterXAbs = new ArrayList<>();
private SparseArray<Integer> childIndex = new SparseArray<>();
public CustomViewPager(Context context) {
super(context);
init();
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
setClipToPadding(false);
setOverScrollMode(OVER_SCROLL_NEVER);
}
/**
* @param childCount
* @param n
* @return 第n个位置的child 的绘制索引
*/
@Override
protected int getChildDrawingOrder(int childCount, int n) {
if (n == 0 || childIndex.size() != childCount) {
childCenterXAbs.clear();
childIndex.clear();
int viewCenterX = getViewCenterX(this);
for (int i = 0; i < childCount; ++i) {
int indexAbs = Math.abs(viewCenterX - getViewCenterX(getChildAt(i)));
//两个距离相同后来的那个做自增从而保持abs不同
if (childIndex.get(indexAbs) != null) {
++indexAbs;
}
childCenterXAbs.add(indexAbs);
childIndex.append(indexAbs, i);
}
Collections.sort(childCenterXAbs);//1,0,2 0,1,2
}
//那个item距离中心点远一些就先draw它。最近的就是中间放大的item,最后draw
return childIndex.get(childCenterXAbs.get(childCount - 1 - n));
}
private int getViewCenterX(View view) {
int[] array = new int[2];
view.getLocationOnScreen(array);
return array[0] + view.getWidth() / 2;
}
}

View File

@@ -0,0 +1,18 @@
package com.fengliyan.uikit.mzbanner;
import android.view.View;
import android.view.ViewGroup;
public interface ICoverFlowAdapter {
int getCount();
Object getItem(int position);
long getItemId(int position);
View getView(int position, View convertView, ViewGroup parent);
void getData(View view, int position);
}

View File

@@ -0,0 +1,741 @@
package com.fengliyan.uikit.mzbanner;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StyleRes;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.mzbanner.holder.MZHolderCreator;
import com.fengliyan.uikit.mzbanner.holder.MZViewHolder;
import com.fengliyan.uikit.mzbanner.transformer.CoverModeTransformer;
import com.fengliyan.uikit.mzbanner.transformer.ScaleYTransformer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zhouwei on 17/5/26.
*/
public class MZBannerView<T> extends RelativeLayout {
private static final String TAG = "MZBannerView";
private CustomViewPager mViewPager;
private MZPagerAdapter mAdapter;
private List<T> mDatas;
private boolean mIsAutoPlay = true;// 是否自动播放
private int mCurrentItem = 0;//当前位置
private Handler mHandler = new Handler();
private int mDelayedTime = 3000;// Banner 切换时间间隔
private ViewPagerScroller mViewPagerScroller;//控制ViewPager滑动速度的Scroller
private boolean mIsOpenMZEffect = true;// 开启魅族Banner效果
private boolean mIsCanLoop = true;// 是否轮播图片
private LinearLayout mIndicatorContainer;//indicator容器
private ArrayList<ImageView> mIndicators = new ArrayList<>();
//mIndicatorRes[0] 为为选中mIndicatorRes[1]为选中
private int []mIndicatorRes= new int[]{R.drawable.indicator_normal,R.drawable.indicator_selected};
private int mIndicatorPaddingLeft = 0;// indicator 距离左边的距离
private int mIndicatorPaddingRight = 0;//indicator 距离右边的距离
private int mIndicatorPaddingTop = 0;//indicator 距离上边的距离
private int mIndicatorPaddingBottom = 0;//indicator 距离下边的距离
private int mMZModePadding = 0;//在仿魅族模式下由于前后显示了上下一个页面的部分因此需要计算这部分padding
private int mIndicatorAlign = 1;
private ViewPager.OnPageChangeListener mOnPageChangeListener;
private BannerPageClickListener mBannerPageClickListener;
public enum IndicatorAlign{
LEFT,//做对齐
CENTER,//居中对齐
RIGHT //右对齐
}
public int getCurrentIndex(){
return mViewPager.getCurrentItem();
}
/**
* 中间Page是否覆盖两边默认覆盖
* @return
*/
private boolean mIsMiddlePageCover = true;
public MZBannerView(@NonNull Context context) {
super(context);
init();
}
public MZBannerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
readAttrs(context,attrs);
init();
}
public MZBannerView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
readAttrs(context,attrs);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MZBannerView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
readAttrs(context,attrs);
init();
}
private void readAttrs(Context context,AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MZBannerView);
mIsOpenMZEffect = typedArray.getBoolean(R.styleable.MZBannerView_open_mz_mode,true);
mIsMiddlePageCover = typedArray.getBoolean(R.styleable.MZBannerView_middle_page_cover,true);
mIsCanLoop = typedArray.getBoolean(R.styleable.MZBannerView_canLoop,true);
mIndicatorAlign = typedArray.getInt(R.styleable.MZBannerView_indicatorAlign,IndicatorAlign.CENTER.ordinal());
mIndicatorPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MZBannerView_indicatorPaddingLeft,0);
mIndicatorPaddingRight = typedArray.getDimensionPixelSize(R.styleable.MZBannerView_indicatorPaddingRight,0);
mIndicatorPaddingTop = typedArray.getDimensionPixelSize(R.styleable.MZBannerView_indicatorPaddingTop,0);
mIndicatorPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MZBannerView_indicatorPaddingBottom,0);
typedArray.recycle();
}
private void init(){
View view = null;
if(mIsOpenMZEffect){
view = LayoutInflater.from(getContext()).inflate(R.layout.mz_banner_effect_layout,this,true);
}else{
view = LayoutInflater.from(getContext()).inflate(R.layout.mz_banner_normal_layout,this,true);
}
mIndicatorContainer = (LinearLayout) view.findViewById(R.id.banner_indicator_container);
mViewPager = (CustomViewPager) view.findViewById(R.id.mzbanner_vp);
mViewPager.setOffscreenPageLimit(4);
mMZModePadding = dpToPx(30);
// 初始化Scroller
initViewPagerScroll();
sureIndicatorPosition();
}
/**
* 是否开启魅族模式
*/
private void setOpenMZEffect(){
// 魅族模式
if(mIsOpenMZEffect){
if(mIsMiddlePageCover){
// 中间页面覆盖两边和魅族APP 的banner 效果一样。
mViewPager.setPageTransformer(true,new CoverModeTransformer(mViewPager));
}else{
// 中间页面不覆盖页面并排只是Y轴缩小
mViewPager.setPageTransformer(false,new ScaleYTransformer());
}
}
}
/**
* make sure the indicator
*/
private void sureIndicatorPosition(){
if(mIndicatorAlign == IndicatorAlign.LEFT.ordinal()){
setIndicatorAlign(IndicatorAlign.LEFT);
}else if(mIndicatorAlign == IndicatorAlign.CENTER.ordinal()){
setIndicatorAlign(IndicatorAlign.CENTER);
}else{
setIndicatorAlign(IndicatorAlign.RIGHT);
}
}
/**
* 设置ViewPager的滑动速度
* */
private void initViewPagerScroll() {
try {
Field mScroller = null;
mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
mViewPagerScroller = new ViewPagerScroller(
mViewPager.getContext());
mScroller.set(mViewPager, mViewPagerScroller);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private final Runnable mLoopRunnable = new Runnable() {
@Override
public void run() {
if(mIsAutoPlay){
mCurrentItem = mViewPager.getCurrentItem();
mCurrentItem++;
if(mCurrentItem == mAdapter.getCount() - 1){
mCurrentItem = 0;
mViewPager.setCurrentItem(mCurrentItem,false);
mHandler.postDelayed(this,mDelayedTime);
}else{
mViewPager.setCurrentItem(mCurrentItem);
mHandler.postDelayed(this,mDelayedTime);
}
}else{
mHandler.postDelayed(this,mDelayedTime);
}
}
};
/**
* 初始化指示器Indicator
*/
private void initIndicator(){
mIndicatorContainer.removeAllViews();
mIndicators.clear();
for(int i=0;i<mDatas.size();i++){
ImageView imageView = new ImageView(getContext());
if(mIndicatorAlign == IndicatorAlign.LEFT.ordinal()){
if(i == 0){
int paddingLeft = mIsOpenMZEffect ? mIndicatorPaddingLeft+mMZModePadding:mIndicatorPaddingLeft;
imageView.setPadding(paddingLeft+6,0,6,0);
} else{
imageView.setPadding(6,0,6,0);
}
}else if(mIndicatorAlign == IndicatorAlign.RIGHT.ordinal()){
if(i == mDatas.size() - 1){
int paddingRight = mIsOpenMZEffect ? mMZModePadding + mIndicatorPaddingRight:mIndicatorPaddingRight;
imageView.setPadding(6,0,6 + paddingRight,0);
}else{
imageView.setPadding(6,0,6,0);
}
}else{
imageView.setPadding(6,0,6,0);
}
if(i == (mCurrentItem % mDatas.size())){
imageView.setImageResource(mIndicatorRes[1]);
}else{
imageView.setImageResource(mIndicatorRes[0]);
}
mIndicators.add(imageView);
mIndicatorContainer.addView(imageView);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!mIsCanLoop){
return super.dispatchTouchEvent(ev);
}
switch (ev.getAction()){
// 按住Banner的时候停止自动轮播
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_DOWN:
int paddingLeft = mViewPager.getLeft();
float touchX = ev.getRawX();
// 如果是魅族模式,去除两边的区域
if(touchX >= paddingLeft && touchX < getScreenWidth(getContext()) - paddingLeft){
pause();
}
break;
case MotionEvent.ACTION_UP:
start();
break;
}
return super.dispatchTouchEvent(ev);
}
public static int getScreenWidth(Context context){
Resources resources = context.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
int width = dm.widthPixels;
return width;
}
/******************************************************************************************************/
/** 对外API **/
/******************************************************************************************************/
/**
* 开始轮播
* <p>应该确保在调用用了{@link MZBannerView {@link #setPages(List, MZHolderCreator)}} 之后调用这个方法开始轮播</p>
*/
public void start(){
// 如果Adapter为null, 说明还没有设置数据这个时候不应该轮播Banner
if(mAdapter== null){
return;
}
if(mIsCanLoop){
pause();
// mIsAutoPlay = true;
// mHandler.postDelayed(mLoopRunnable,mDelayedTime);
}
}
/**
* 停止轮播
*/
public void pause(){
mIsAutoPlay = false;
mHandler.removeCallbacks(mLoopRunnable);
}
/**
* 设置是否可以轮播
* @param canLoop
*/
public void setCanLoop(boolean canLoop){
mIsCanLoop = canLoop;
if(!canLoop){
pause();
}
}
/**
* 设置BannerView 的切换时间间隔
* @param delayedTime
*/
public void setDelayedTime(int delayedTime) {
mDelayedTime = delayedTime;
}
public void addPageChangeListener(ViewPager.OnPageChangeListener onPageChangeListener){
mOnPageChangeListener = onPageChangeListener;
}
/**
* 添加Page点击事件
* @param bannerPageClickListener {@link BannerPageClickListener}
*/
public void setBannerPageClickListener(BannerPageClickListener bannerPageClickListener) {
mBannerPageClickListener = bannerPageClickListener;
}
/**
* 是否显示Indicator
* @param visible true 显示Indicator否则不显示
*/
public void setIndicatorVisible(boolean visible){
if(visible){
mIndicatorContainer.setVisibility(VISIBLE);
}else{
mIndicatorContainer.setVisibility(GONE);
}
}
/**
* set indicator padding
* @param paddingLeft
* @param paddingTop
* @param paddingRight
* @param paddingBottom
*/
public void setIndicatorPadding(int paddingLeft,int paddingTop,int paddingRight,int paddingBottom){
mIndicatorPaddingLeft = paddingLeft;
mIndicatorPaddingTop = paddingTop;
mIndicatorPaddingRight = paddingRight;
mIndicatorPaddingBottom = paddingBottom;
sureIndicatorPosition();
}
/**
* 返回ViewPager
* @return {@link ViewPager}
*/
public ViewPager getViewPager() {
return mViewPager;
}
/**
* 设置indicator 图片资源
* @param unSelectRes 未选中状态资源图片
* @param selectRes 选中状态资源图片
*/
public void setIndicatorRes(@DrawableRes int unSelectRes, @DrawableRes int selectRes){
mIndicatorRes[0]= unSelectRes;
mIndicatorRes[1] = selectRes;
}
/**
* 设置数据,这是最重要的一个方法。
* <p>其他的配置应该在这个方法之前调用</p>
* @param datas Banner 展示的数据集合
* @param mzHolderCreator ViewHolder生成器 {@link MZHolderCreator} And {@link MZViewHolder}
*/
public void setPages(List<T> datas, MZHolderCreator mzHolderCreator){
if(datas == null || mzHolderCreator == null){
return;
}
mDatas = datas;
//如果在播放,就先让播放停止
pause();
//增加一个逻辑由于魅族模式会在一个页面展示前后页面的部分因此数据集合的长度至少为3,否则自动为普通Banner模式
//不管配置的:open_mz_mode 属性的值是否为true
if(datas.size() < 3){
mIsOpenMZEffect = false;
MarginLayoutParams layoutParams = (MarginLayoutParams) mViewPager.getLayoutParams();
layoutParams.setMargins(0,0,0,0);
mViewPager.setLayoutParams(layoutParams);
setClipChildren(true);
mViewPager.setClipChildren(true);
}
setOpenMZEffect();
// 2017.7.20 fix将Indicator初始化放在Adapter的初始化之前解决更新数据变化更新时crush.
//初始化Indicator
initIndicator();
mAdapter = new MZPagerAdapter(datas,mzHolderCreator,mIsCanLoop);
mAdapter.setUpViewViewPager(mViewPager);
mAdapter.setPageClickListener(mBannerPageClickListener);
mViewPager.clearOnPageChangeListeners();
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int realPosition = position % mIndicators.size();
if(mOnPageChangeListener!=null){
mOnPageChangeListener.onPageScrolled(realPosition,positionOffset,positionOffsetPixels);
}
}
@Override
public void onPageSelected(int position) {
mCurrentItem = position;
// 切换indicator
int realSelectPosition = mCurrentItem % mIndicators.size();
for(int i = 0;i<mDatas.size();i++){
if(i == realSelectPosition){
mIndicators.get(i).setImageResource(mIndicatorRes[1]);
}else{
mIndicators.get(i).setImageResource(mIndicatorRes[0]);
}
}
// 不能直接将mOnPageChangeListener 设置给ViewPager ,否则拿到的position 是原始的position
if(mOnPageChangeListener!=null){
mOnPageChangeListener.onPageSelected(realSelectPosition);
}
}
@Override
public void onPageScrollStateChanged(int state) {
switch (state){
case ViewPager.SCROLL_STATE_DRAGGING:
mIsAutoPlay = false;
break;
case ViewPager.SCROLL_STATE_SETTLING:
mIsAutoPlay = true;
break;
}
if(mOnPageChangeListener!=null){
mOnPageChangeListener.onPageScrollStateChanged(state);
}
}
});
}
/**
* 设置Indicator 的对齐方式
* @param indicatorAlign {@link IndicatorAlign#CENTER }{@link IndicatorAlign#LEFT }{@link IndicatorAlign#RIGHT }
*/
public void setIndicatorAlign(IndicatorAlign indicatorAlign) {
mIndicatorAlign = indicatorAlign.ordinal();
LayoutParams layoutParams = (LayoutParams) mIndicatorContainer.getLayoutParams();
if(indicatorAlign == IndicatorAlign.LEFT){
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}else if(indicatorAlign == IndicatorAlign.RIGHT){
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
}else{
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
}
// 2017.8.27 添加增加设置Indicator 的上下边距。
layoutParams.setMargins(0,mIndicatorPaddingTop,0,mIndicatorPaddingBottom);
mIndicatorContainer.setLayoutParams(layoutParams);
}
public LinearLayout getIndicatorContainer() {
return mIndicatorContainer;
}
/**
* 设置ViewPager切换的速度
* @param duration 切换动画时间
*/
public void setDuration(int duration){
mViewPagerScroller.setDuration(duration);
}
/**
* 设置是否使用ViewPager默认是的切换速度
* @param useDefaultDuration 切换动画时间
*/
public void setUseDefaultDuration(boolean useDefaultDuration){
mViewPagerScroller.setUseDefaultDuration(useDefaultDuration);
}
/**
* 获取Banner页面切换动画时间
* @return
*/
public int getDuration(){
return mViewPagerScroller.getScrollDuration();
}
public static class MZPagerAdapter<T> extends PagerAdapter {
private List<T> mDatas;
private MZHolderCreator mMZHolderCreator;
private ViewPager mViewPager;
private boolean canLoop;
private BannerPageClickListener mPageClickListener;
private final int mLooperCountFactor = 500;
public MZPagerAdapter(List<T> datas, MZHolderCreator MZHolderCreator,boolean canLoop) {
if(mDatas == null){
mDatas = new ArrayList<>();
}
//mDatas.add(datas.get(datas.size()-1));// 加入最后一个
for(T t:datas){
mDatas.add(t);
}
// mDatas.add(datas.get(0));//在最后加入最前面一个
mMZHolderCreator = MZHolderCreator;
this.canLoop = canLoop;
}
public void setPageClickListener(BannerPageClickListener pageClickListener) {
mPageClickListener = pageClickListener;
}
/**
* 初始化Adapter和设置当前选中的Item
* @param viewPager
*/
public void setUpViewViewPager(ViewPager viewPager){
mViewPager = viewPager;
mViewPager.setAdapter(this);
mViewPager.getAdapter().notifyDataSetChanged();
int currentItem = canLoop ? getStartSelectItem():0;
//设置当前选中的Item
mViewPager.setCurrentItem(currentItem);
}
private int getStartSelectItem(){
if(getRealCount() == 0){
return 0;
}
// 我们设置当前选中的位置为Integer.MAX_VALUE / 2,这样开始就能往左滑动
// 但是要保证这个值与getRealPosition 的 余数为0因为要从第一页开始显示
int currentItem = getRealCount() * mLooperCountFactor / 2;
if(currentItem % getRealCount() ==0 ){
return currentItem;
}
// 直到找到从0开始的位置
while (currentItem % getRealCount() != 0){
currentItem++;
}
return currentItem;
}
public void setDatas(List<T> datas) {
mDatas = datas;
}
@Override
public int getCount() {
// 2017.6.10 bug fix
// 如果getCount 的返回值为Integer.MAX_VALUE 的话那么在setCurrentItem的时候会ANR(除了在onCreate 调用之外)
return canLoop ? getRealCount() * mLooperCountFactor : getRealCount();//ViewPager返回int 最大值
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
View view = getView(position,container);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public void finishUpdate(ViewGroup container) {
// 轮播模式才执行
if(canLoop){
int position = mViewPager.getCurrentItem();
if (position == getCount() - 1) {
position = 0;
setCurrentItem(position);
}
}
}
private void setCurrentItem(int position){
try {
mViewPager.setCurrentItem(position, false);
}catch (IllegalStateException e){
e.printStackTrace();
}
}
/**
* 获取真实的Count
* @return
*/
private int getRealCount(){
return mDatas==null ? 0:mDatas.size();
}
/**
*
* @param position
* @param container
* @return
*/
private View getView(int position,ViewGroup container){
final int realPosition = position % getRealCount();
MZViewHolder holder =null;
// create holder
holder = mMZHolderCreator.createViewHolder();
if(holder == null){
throw new RuntimeException("can not return a null holder");
}
// create View
View view = holder.createView(container.getContext());
if( mDatas!=null && mDatas.size()>0){
holder.onBind(container.getContext(),realPosition,mDatas.get(realPosition));
}
// 添加点击事件
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(mPageClickListener!=null){
mPageClickListener.onPageClick(v,realPosition);
}
}
});
return view;
}
}
/**
*
由于ViewPager 默认的切换速度有点快因此用一个Scroller 来控制切换的速度
* <p>而实际上ViewPager 切换本来就是用的Scroller来做的因此我们可以通过反射来</p>
* <p>获取取到ViewPager 的 mScroller 属性然后替换成我们自己的Scroller</p>
*/
public static class ViewPagerScroller extends Scroller{
private int mDuration = 800;// ViewPager默认的最大Duration 为600,我们默认稍微大一点。值越大越慢。
private boolean mIsUseDefaultDuration = false;
public ViewPagerScroller(Context context) {
super(context);
}
public ViewPagerScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy) {
super.startScroll(startX, startY, dx, dy,mDuration);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, mIsUseDefaultDuration?duration:mDuration);
}
public void setUseDefaultDuration(boolean useDefaultDuration) {
mIsUseDefaultDuration = useDefaultDuration;
}
public boolean isUseDefaultDuration() {
return mIsUseDefaultDuration;
}
public void setDuration(int duration) {
mDuration = duration;
}
public int getScrollDuration() {
return mDuration;
}
}
/**
* Banner page 点击回调
*/
public interface BannerPageClickListener{
void onPageClick(View view, int position);
}
public static int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}
}

View File

@@ -0,0 +1,13 @@
package com.fengliyan.uikit.mzbanner.holder;
/**
* Created by zhouwei on 17/5/26.
*/
public interface MZHolderCreator<VH extends MZViewHolder> {
/**
* 创建ViewHolder
* @return
*/
public VH createViewHolder();
}

View File

@@ -0,0 +1,25 @@
package com.fengliyan.uikit.mzbanner.holder;
import android.content.Context;
import android.view.View;
/**
* Created by zhouwei on 17/5/26.
*/
public interface MZViewHolder<T> {
/**
* 创建View
* @param context
* @return
*/
View createView(Context context);
/**
* 绑定数据
* @param context
* @param position
* @param data
*/
void onBind(Context context, int position, T data);
}

View File

@@ -0,0 +1,64 @@
package com.fengliyan.uikit.mzbanner.transformer;
import android.view.View;
import androidx.viewpager.widget.ViewPager;
/**
* Created by zhouwei on 17/8/20.
*/
public class CoverModeTransformer implements ViewPager.PageTransformer {
private float reduceX = 0.0f;
private float itemWidth = 0;
private float offsetPosition = 0f;
private int mCoverWidth;
private float mScaleMax = 1.0f;
private float mScaleMin = 0.9f;
private ViewPager mViewPager;
public CoverModeTransformer(ViewPager pager){
mViewPager = pager;
}
@Override
public void transformPage(View view, float position) {
if (offsetPosition == 0f) {
float paddingLeft = mViewPager.getPaddingLeft();
float paddingRight = mViewPager.getPaddingRight();
float width = mViewPager.getMeasuredWidth();
offsetPosition = paddingLeft / (width - paddingLeft - paddingRight);
}
float currentPos = position - offsetPosition;
if (itemWidth == 0) {
itemWidth = view.getWidth();
//由于左右边的缩小而减小的x的大小的一半
reduceX = (2.0f - mScaleMax - mScaleMin) * itemWidth / 2.0f;
}
if (currentPos <= -1.0f) {
view.setTranslationX(reduceX + mCoverWidth);
view.setScaleX(mScaleMin);
view.setScaleY(mScaleMin);
} else if (currentPos <= 1.0) {
float scale = (mScaleMax - mScaleMin) * Math.abs(1.0f - Math.abs(currentPos));
float translationX = currentPos * -reduceX;
if (currentPos <= -0.5) {//两个view中间的临界这时两个view在同一层左侧View需要往X轴正方向移动覆盖的值()
view.setTranslationX(translationX + mCoverWidth * Math.abs(Math.abs(currentPos) - 0.5f) / 0.5f);
} else if (currentPos <= 0.0f) {
view.setTranslationX(translationX);
} else if (currentPos >= 0.5) {//两个view中间的临界这时两个view在同一层
view.setTranslationX(translationX - mCoverWidth * Math.abs(Math.abs(currentPos) - 0.5f) / 0.5f);
} else {
view.setTranslationX(translationX);
}
view.setScaleX(scale + mScaleMin);
view.setScaleY(scale + mScaleMin);
} else {
view.setScaleX(mScaleMin);
view.setScaleY(mScaleMin);
view.setTranslationX(-reduceX - mCoverWidth);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.fengliyan.uikit.mzbanner.transformer;
import android.view.View;
import androidx.viewpager.widget.ViewPager;
/**
* Created by zhouwei on 17/5/26.
*/
public class ScaleYTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.9F;
@Override
public void transformPage(View page, float position) {
if(position < -1){
page.setScaleY(MIN_SCALE);
}else if(position<= 1){
//
float scale = Math.max(MIN_SCALE,1 - Math.abs(position));
page.setScaleY(scale);
/*page.setScaleX(scale);
if(position<0){
page.setTranslationX(width * (1 - scale) /2);
}else{
page.setTranslationX(-width * (1 - scale) /2);
}*/
}else{
page.setScaleY(MIN_SCALE);
}
}
}

View File

@@ -0,0 +1,121 @@
package com.fengliyan.uikit.photopicker;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.fengliyan.base.base.permission.AbsPermissionResultCallBack;
import com.fengliyan.base.base.permission.PermissionHelper;
import java.util.ArrayList;
/**
* 图片选择器
* Created by nereo on 16/3/17.
*/
public class MultiImageSelector {
public static final String EXTRA_RESULT = MultiImageSelectorActivity.EXTRA_RESULT;
private boolean mShowCamera = true;
private int mMaxCount = 9;
private int mMode = MultiImageSelectorActivity.MODE_MULTI;
private ArrayList<String> mOriginData;
private static MultiImageSelector sSelector;
@Deprecated
private MultiImageSelector(Context context) {
}
private MultiImageSelector() {
}
@Deprecated
public static MultiImageSelector create(Context context) {
if (sSelector == null) {
sSelector = new MultiImageSelector(context);
}
return sSelector;
}
public static MultiImageSelector create() {
if (sSelector == null) {
sSelector = new MultiImageSelector();
}
return sSelector;
}
public MultiImageSelector showCamera(boolean show) {
mShowCamera = show;
return sSelector;
}
public MultiImageSelector count(int count) {
mMaxCount = count;
return sSelector;
}
public MultiImageSelector single() {
mMode = MultiImageSelectorActivity.MODE_SINGLE;
return sSelector;
}
public MultiImageSelector multi() {
mMode = MultiImageSelectorActivity.MODE_MULTI;
return sSelector;
}
public MultiImageSelector origin(ArrayList<String> images) {
mOriginData = images;
return sSelector;
}
public void start(final Activity activity, final int requestCode) {
final Context context = activity;
PermissionHelper.request(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
new AbsPermissionResultCallBack() {
@Override
public void onPermissionGranted() {
activity.startActivityForResult(createIntent(context), requestCode);
}
});
}
public void start(final Fragment fragment, final int requestCode) {
final Context context = fragment.getContext();
PermissionHelper.request(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
new AbsPermissionResultCallBack() {
@Override
public void onPermissionGranted() {
fragment.startActivityForResult(createIntent(context), requestCode);
}
});
}
private boolean hasPermission(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// Permission was added in API Level 16
return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
}
return true;
}
private Intent createIntent(Context context) {
Intent intent = new Intent(context, MultiImageSelectorActivity.class);
intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, mShowCamera);
intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, mMaxCount);
if (mOriginData != null) {
intent.putStringArrayListExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, mOriginData);
}
intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, mMode);
return intent;
}
}

View File

@@ -0,0 +1,182 @@
package com.fengliyan.uikit.photopicker;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import com.fengliyan.uikit.R;
import java.io.File;
import java.util.ArrayList;
/**
* Multi image selector
* Created by Nereo on 2015/4/7.
* Updated by nereo on 2016/1/19.
* Updated by nereo on 2016/5/18.
*/
public class MultiImageSelectorActivity extends AppCompatActivity
implements MultiImageSelectorFragment.Callback{
// Single choice
public static final int MODE_SINGLE = 0;
// Multi choice
public static final int MODE_MULTI = 1;
/** Max image sizeint{@link #DEFAULT_IMAGE_SIZE} by default */
public static final String EXTRA_SELECT_COUNT = "max_select_count";
/** Select mode{@link #MODE_MULTI} by default */
public static final String EXTRA_SELECT_MODE = "select_count_mode";
/** Whether show cameratrue by default */
public static final String EXTRA_SHOW_CAMERA = "show_camera";
/** Result data setArrayList&lt;String&gt;*/
public static final String EXTRA_RESULT = "select_result";
/** Original data set */
public static final String EXTRA_DEFAULT_SELECTED_LIST = "default_list";
// Default image size
private static final int DEFAULT_IMAGE_SIZE = 9;
private ArrayList<String> resultList = new ArrayList<>();
private Button mSubmitButton;
private int mDefaultCount = DEFAULT_IMAGE_SIZE;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.MIS_NO_ACTIONBAR);
setContentView(R.layout.mis_activity_default);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.BLACK);
}
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if(toolbar != null){
setSupportActionBar(toolbar);
}
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
final Intent intent = getIntent();
mDefaultCount = intent.getIntExtra(EXTRA_SELECT_COUNT, DEFAULT_IMAGE_SIZE);
final int mode = intent.getIntExtra(EXTRA_SELECT_MODE, MODE_MULTI);
final boolean isShow = intent.getBooleanExtra(EXTRA_SHOW_CAMERA, true);
if(mode == MODE_MULTI && intent.hasExtra(EXTRA_DEFAULT_SELECTED_LIST)) {
resultList = intent.getStringArrayListExtra(EXTRA_DEFAULT_SELECTED_LIST);
}
mSubmitButton = (Button) findViewById(R.id.commit);
if(mode == MODE_MULTI){
updateDoneText(resultList);
mSubmitButton.setVisibility(View.VISIBLE);
mSubmitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(resultList != null && resultList.size() >0){
// Notify success
Intent data = new Intent();
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
}else{
setResult(RESULT_CANCELED);
}
finish();
}
});
}else{
mSubmitButton.setVisibility(View.GONE);
}
if(savedInstanceState == null){
Bundle bundle = new Bundle();
bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
bundle.putBoolean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
bundle.putStringArrayList(MultiImageSelectorFragment.EXTRA_DEFAULT_SELECTED_LIST, resultList);
getSupportFragmentManager().beginTransaction()
.add(R.id.image_grid, Fragment.instantiate(this, MultiImageSelectorFragment.class.getName(), bundle))
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
setResult(RESULT_CANCELED);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Update done button by select image data
* @param resultList selected image data
*/
private void updateDoneText(ArrayList<String> resultList){
int size = 0;
if(resultList == null || resultList.size()<=0){
mSubmitButton.setText(R.string.mis_action_done);
mSubmitButton.setEnabled(false);
}else{
size = resultList.size();
mSubmitButton.setEnabled(true);
}
mSubmitButton.setText(getString(R.string.mis_action_button_string,
getString(R.string.mis_action_done), size, mDefaultCount));
}
@Override
public void onSingleImageSelected(String path) {
Intent data = new Intent();
resultList.add(path);
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
@Override
public void onImageSelected(String path) {
if(!resultList.contains(path)) {
resultList.add(path);
}
updateDoneText(resultList);
}
@Override
public void onImageUnselected(String path) {
if(resultList.contains(path)){
resultList.remove(path);
}
updateDoneText(resultList);
}
@Override
public void onCameraShot(File imageFile) {
if(imageFile != null) {
// notify system the image has change
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
Intent data = new Intent();
resultList.add(imageFile.getAbsolutePath());
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
}
}

View File

@@ -0,0 +1,576 @@
package com.fengliyan.uikit.photopicker;
import android.Manifest;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListPopupWindow;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import com.fengliyan.base.base.permission.PermissionDialog;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.photopicker.adapter.FolderAdapter;
import com.fengliyan.uikit.photopicker.adapter.ImageGridAdapter;
import com.fengliyan.uikit.photopicker.bean.Folder;
import com.fengliyan.uikit.photopicker.bean.Image;
import com.fengliyan.uikit.photopicker.utils.FileUtils;
import com.fengliyan.uikit.photopicker.utils.ScreenUtils;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Multi image selector Fragment
* Created by Nereo on 2015/4/7.
* Updated by nereo on 2016/5/18.
*/
public class MultiImageSelectorFragment extends Fragment {
public static final String TAG = "MultiImageSelectorFragment";
private static final int REQUEST_STORAGE_WRITE_ACCESS_PERMISSION = 110;
private static final int REQUEST_CAMERA = 100;
private static final String KEY_TEMP_FILE = "key_temp_file";
// Single choice
public static final int MODE_SINGLE = 0;
// Multi choice
public static final int MODE_MULTI = 1;
/**
* Max image sizeint
*/
public static final String EXTRA_SELECT_COUNT = "max_select_count";
/**
* Select mode{@link #MODE_MULTI} by default
*/
public static final String EXTRA_SELECT_MODE = "select_count_mode";
/**
* Whether show cameratrue by default
*/
public static final String EXTRA_SHOW_CAMERA = "show_camera";
/**
* Original data set
*/
public static final String EXTRA_DEFAULT_SELECTED_LIST = "default_list";
// loaders
private static final int LOADER_ALL = 0;
private static final int LOADER_CATEGORY = 1;
// image result data set
private ArrayList<String> resultList = new ArrayList<>();
// folder result data set
private ArrayList<Folder> mResultFolder = new ArrayList<>();
private GridView mGridView;
private Callback mCallback;
private ImageGridAdapter mImageAdapter;
private FolderAdapter mFolderAdapter;
private ListPopupWindow mFolderPopupWindow;
private TextView mCategoryText;
private View mPopupAnchorView;
private boolean hasFolderGened = false;
private File mTmpFile;
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (Callback) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException("The Activity must implement MultiImageSelectorFragment.Callback interface...");
}
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.mis_fragment_multi_image, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final int mode = selectMode();
if (mode == MODE_MULTI) {
ArrayList<String> tmp = getArguments().getStringArrayList(EXTRA_DEFAULT_SELECTED_LIST);
if (tmp != null && tmp.size() > 0) {
resultList = tmp;
}
}
mImageAdapter = new ImageGridAdapter(getActivity(), showCamera(), 3);
mImageAdapter.showSelectIndicator(mode == MODE_MULTI);
mPopupAnchorView = view.findViewById(R.id.footer);
mCategoryText = (TextView) view.findViewById(R.id.category_btn);
mCategoryText.setText(R.string.mis_folder_all);
mCategoryText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mFolderPopupWindow == null) {
createPopupFolderList();
}
if (mFolderPopupWindow.isShowing()) {
mFolderPopupWindow.dismiss();
} else {
mFolderPopupWindow.show();
int index = mFolderAdapter.getSelectIndex();
index = index == 0 ? index : index - 1;
mFolderPopupWindow.getListView().setSelection(index);
}
}
});
mGridView = (GridView) view.findViewById(R.id.grid);
mGridView.setAdapter(mImageAdapter);
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (mImageAdapter.isShowCamera()) {
if (i == 0) {
showCameraAction();
} else {
Image image = (Image) adapterView.getAdapter().getItem(i);
selectImageFromGrid(image, mode);
}
} else {
Image image = (Image) adapterView.getAdapter().getItem(i);
selectImageFromGrid(image, mode);
}
}
});
mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_FLING) {
Picasso.with(view.getContext()).pauseTag(TAG);
} else {
Picasso.with(view.getContext()).resumeTag(TAG);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
mFolderAdapter = new FolderAdapter(getActivity());
}
/**
* Create popup ListView
*/
private void createPopupFolderList() {
Point point = ScreenUtils.getScreenSize(getActivity());
int width = point.x;
int height = (int) (point.y * (4.5f / 8.0f));
mFolderPopupWindow = new ListPopupWindow(getActivity());
mFolderPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
mFolderPopupWindow.setAdapter(mFolderAdapter);
mFolderPopupWindow.setContentWidth(width);
mFolderPopupWindow.setWidth(width);
mFolderPopupWindow.setHeight(height);
mFolderPopupWindow.setAnchorView(mPopupAnchorView);
mFolderPopupWindow.setModal(true);
mFolderPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
mFolderAdapter.setSelectIndex(i);
final int index = i;
final AdapterView v = adapterView;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mFolderPopupWindow.dismiss();
if (index == 0) {
getActivity().getSupportLoaderManager().restartLoader(LOADER_ALL, null, mLoaderCallback);
mCategoryText.setText(R.string.mis_folder_all);
if (showCamera()) {
mImageAdapter.setShowCamera(true);
} else {
mImageAdapter.setShowCamera(false);
}
} else {
Folder folder = (Folder) v.getAdapter().getItem(index);
if (null != folder) {
mImageAdapter.setData(folder.images);
mCategoryText.setText(folder.name);
if (resultList != null && resultList.size() > 0) {
mImageAdapter.setDefaultSelected(resultList);
}
}
mImageAdapter.setShowCamera(false);
}
mGridView.smoothScrollToPosition(0);
}
}, 100);
}
});
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(KEY_TEMP_FILE, mTmpFile);
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mTmpFile = (File) savedInstanceState.getSerializable(KEY_TEMP_FILE);
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// load image data
getActivity().getSupportLoaderManager().initLoader(LOADER_ALL, null, mLoaderCallback);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CAMERA) {
if (resultCode == Activity.RESULT_OK) {
if (mTmpFile != null) {
if (mCallback != null) {
mCallback.onCameraShot(mTmpFile);
}
}
} else {
// delete tmp file
while (mTmpFile != null && mTmpFile.exists()) {
boolean success = mTmpFile.delete();
if (success) {
mTmpFile = null;
}
}
}
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mFolderPopupWindow != null) {
if (mFolderPopupWindow.isShowing()) {
mFolderPopupWindow.dismiss();
}
}
super.onConfigurationChanged(newConfig);
}
/**
* Open camera
*/
private void showCameraAction() {
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
getString(R.string.mis_permission_rationale_write_storage),
REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
} else if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestPermission(Manifest.permission.CAMERA,
getString(R.string.mis_permission_rationale_camera),
REQUEST_CAMERA);
} else {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
try {
mTmpFile = FileUtils.createTmpFile(getActivity());
} catch (IOException e) {
e.printStackTrace();
}
if (mTmpFile != null && mTmpFile.exists()) {
intent.putExtra(MediaStore.EXTRA_OUTPUT, getImageContentUri(getActivity(), mTmpFile));
startActivityForResult(intent, REQUEST_CAMERA);
} else {
Toast.makeText(getActivity(), R.string.mis_error_image_not_exist, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getActivity(), R.string.mis_msg_no_camera, Toast.LENGTH_SHORT).show();
}
}
}
public Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media._ID},
MediaStore.Images.Media.DATA + "=? ",
new String[]{filePath}, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor
.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (imageFile.exists()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}
PermissionDialog permissionDialog;
private void requestPermission(final String permission, String rationale, final int requestCode) {
permissionDialog = new PermissionDialog(getContext(), new String[]{permission});
permissionDialog.show();
if (shouldShowRequestPermissionRationale(permission)) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.mis_permission_dialog_title)
.setMessage(rationale)
.setPositiveButton(R.string.mis_permission_dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(new String[]{permission}, requestCode);
}
})
.setNegativeButton(R.string.mis_permission_dialog_cancel, null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (permissionDialog != null && permissionDialog.isShowing()) {
permissionDialog.dismiss();
}
}
})
.create().show();
} else {
requestPermissions(new String[]{permission}, requestCode);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_STORAGE_WRITE_ACCESS_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showCameraAction();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
if (permissionDialog != null && permissionDialog.isShowing()) {
permissionDialog.dismiss();
}
}
/**
* notify callback
*
* @param image image data
*/
private void selectImageFromGrid(Image image, int mode) {
if (image != null) {
if (mode == MODE_MULTI) {
if (resultList.contains(image.path)) {
resultList.remove(image.path);
if (mCallback != null) {
mCallback.onImageUnselected(image.path);
}
} else {
if (selectImageCount() == resultList.size()) {
Toast.makeText(getActivity(), R.string.mis_msg_amount_limit, Toast.LENGTH_SHORT).show();
return;
}
resultList.add(image.path);
if (mCallback != null) {
mCallback.onImageSelected(image.path);
}
}
mImageAdapter.select(image);
} else if (mode == MODE_SINGLE) {
if (mCallback != null) {
mCallback.onSingleImageSelected(image.path);
}
}
}
}
private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() {
private final String[] IMAGE_PROJECTION = {
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media._ID};
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader cursorLoader = null;
if (id == LOADER_ALL) {
cursorLoader = new CursorLoader(getActivity(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
IMAGE_PROJECTION[4] + ">0 AND " + IMAGE_PROJECTION[3] + "=? OR " + IMAGE_PROJECTION[3] + "=? ",
new String[]{"image/jpeg", "image/png"}, IMAGE_PROJECTION[2] + " DESC");
} else if (id == LOADER_CATEGORY) {
cursorLoader = new CursorLoader(getActivity(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
IMAGE_PROJECTION[4] + ">0 AND " + IMAGE_PROJECTION[0] + " like '%" + args.getString("path") + "%'",
null, IMAGE_PROJECTION[2] + " DESC");
}
return cursorLoader;
}
private boolean fileExist(String path) {
if (!TextUtils.isEmpty(path)) {
return new File(path).exists();
}
return false;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (data != null) {
if (data.getCount() > 0) {
List<Image> images = new ArrayList<>();
data.moveToFirst();
do {
String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));
String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));
long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]));
if (!fileExist(path)) {
continue;
}
Image image = null;
if (!TextUtils.isEmpty(name)) {
image = new Image(path, name, dateTime);
images.add(image);
}
if (!hasFolderGened) {
// get all folder data
File folderFile = new File(path).getParentFile();
if (folderFile != null && folderFile.exists()) {
String fp = folderFile.getAbsolutePath();
Folder f = getFolderByPath(fp);
if (f == null) {
Folder folder = new Folder();
folder.name = folderFile.getName();
folder.path = fp;
folder.cover = image;
List<Image> imageList = new ArrayList<>();
imageList.add(image);
folder.images = imageList;
mResultFolder.add(folder);
} else {
f.images.add(image);
}
}
}
} while (data.moveToNext());
mImageAdapter.setData(images);
if (resultList != null && resultList.size() > 0) {
mImageAdapter.setDefaultSelected(resultList);
}
if (!hasFolderGened) {
mFolderAdapter.setData(mResultFolder);
hasFolderGened = true;
}
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
};
private Folder getFolderByPath(String path) {
if (mResultFolder != null) {
for (Folder folder : mResultFolder) {
if (TextUtils.equals(folder.path, path)) {
return folder;
}
}
}
return null;
}
private boolean showCamera() {
return getArguments() == null || getArguments().getBoolean(EXTRA_SHOW_CAMERA, true);
}
private int selectMode() {
return getArguments() == null ? MODE_MULTI : getArguments().getInt(EXTRA_SELECT_MODE);
}
private int selectImageCount() {
return getArguments() == null ? 9 : getArguments().getInt(EXTRA_SELECT_COUNT);
}
/**
* Callback for host activity
*/
public interface Callback {
void onSingleImageSelected(String path);
void onImageSelected(String path);
void onImageUnselected(String path);
void onCameraShot(File imageFile);
}
}

View File

@@ -0,0 +1,192 @@
package com.fengliyan.uikit.photopicker.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.photopicker.bean.Folder;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 文件夹Adapter
* Created by Nereo on 2015/4/7.
* Updated by nereo on 2016/1/19.
*/
public class FolderAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;
private List<Folder> mFolders = new ArrayList<>();
int mImageSize;
int lastSelected = 0;
public FolderAdapter(Context context) {
mContext = context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mImageSize = mContext.getResources().getDimensionPixelOffset(R.dimen.mis_folder_cover_size);
}
/**
* 设置数据集
*
* @param folders
*/
public void setData(List<Folder> folders) {
if (folders != null && folders.size() > 0) {
mFolders = folders;
} else {
mFolders.clear();
}
notifyDataSetChanged();
}
@Override
public int getCount() {
return mFolders.size() + 1;
}
@Override
public Folder getItem(int i) {
if (i == 0) return null;
return mFolders.get(i - 1);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
view = mInflater.inflate(R.layout.mis_list_item_folder, viewGroup, false);
holder = new ViewHolder(view);
} else {
holder = (ViewHolder) view.getTag();
}
if (holder != null) {
if (i == 0) {
holder.name.setText(R.string.mis_folder_all);
holder.path.setText("/sdcard");
holder.size.setText(String.format("%d%s",
getTotalImageSize(), mContext.getResources().getString(R.string.mis_photo_unit)));
if (mFolders.size() > 0) {
Folder f = mFolders.get(0);
if (f != null) {
// RequestOptions requestOptions = new RequestOptions()
// .centerCrop()
// .placeholder(R.drawable.mis_default_error)
// .error(R.drawable.mis_default_error)
// .override(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size);
// Glide.with(mContext).asBitmap()
// .load(new File(f.cover.path))
// .apply(requestOptions)
// .into(holder.cover);
Picasso.with(mContext)
.load(new File(f.cover.path))
.error(R.drawable.mis_default_error)
.resizeDimen(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size)
.centerCrop()
.into(holder.cover);
} else {
holder.cover.setImageResource(R.drawable.mis_default_error);
}
}
} else {
holder.bindData(getItem(i));
}
if (lastSelected == i) {
holder.indicator.setVisibility(View.VISIBLE);
} else {
holder.indicator.setVisibility(View.INVISIBLE);
}
}
return view;
}
private int getTotalImageSize() {
int result = 0;
if (mFolders != null && mFolders.size() > 0) {
for (Folder f : mFolders) {
result += f.images.size();
}
}
return result;
}
public void setSelectIndex(int i) {
if (lastSelected == i) return;
lastSelected = i;
notifyDataSetChanged();
}
public int getSelectIndex() {
return lastSelected;
}
class ViewHolder {
ImageView cover;
TextView name;
TextView path;
TextView size;
ImageView indicator;
ViewHolder(View view) {
cover = (ImageView) view.findViewById(R.id.cover);
name = (TextView) view.findViewById(R.id.name);
path = (TextView) view.findViewById(R.id.path);
size = (TextView) view.findViewById(R.id.size);
indicator = (ImageView) view.findViewById(R.id.indicator);
view.setTag(this);
}
void bindData(Folder data) {
if (data == null) {
return;
}
name.setText(data.name);
path.setText(data.path);
if (data.images != null) {
size.setText(String.format("%d%s", data.images.size(), mContext.getResources().getString(R.string.mis_photo_unit)));
} else {
size.setText("*" + mContext.getResources().getString(R.string.mis_photo_unit));
}
if (data.cover != null) {
// 显示图片
// RequestOptions requestOptions = new RequestOptions()
// .centerCrop()
// .placeholder(R.drawable.mis_default_error)
// .error(R.drawable.mis_default_error)
// .override(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size);
// Glide.with(mContext).asBitmap()
// .load(new File(data.cover.path))
// .apply(requestOptions)
// .into(cover);
Picasso.with(mContext)
.load(new File(data.cover.path))
.placeholder(R.drawable.mis_default_error)
.resizeDimen(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size)
.centerCrop()
.into(cover);
} else {
cover.setImageResource(R.drawable.mis_default_error);
}
}
}
}

View File

@@ -0,0 +1,248 @@
package com.fengliyan.uikit.photopicker.adapter;
import android.content.Context;
import android.graphics.Point;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.photopicker.bean.Image;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
;
/**
* 图片Adapter
* Created by Nereo on 2015/4/7.
* Updated by nereo on 2016/1/19.
*/
public class ImageGridAdapter extends BaseAdapter {
private static final int TYPE_CAMERA = 0;
private static final int TYPE_NORMAL = 1;
private Context mContext;
private LayoutInflater mInflater;
private boolean showCamera = true;
private boolean showSelectIndicator = true;
private List<Image> mImages = new ArrayList<>();
private List<Image> mSelectedImages = new ArrayList<>();
final int mGridWidth;
public ImageGridAdapter(Context context, boolean showCamera, int column){
mContext = context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.showCamera = showCamera;
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
int width = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
Point size = new Point();
wm.getDefaultDisplay().getSize(size);
width = size.x;
}else{
width = wm.getDefaultDisplay().getWidth();
}
mGridWidth = width / column;
}
/**
* 显示选择指示器
* @param b
*/
public void showSelectIndicator(boolean b) {
showSelectIndicator = b;
}
public void setShowCamera(boolean b){
if(showCamera == b) return;
showCamera = b;
notifyDataSetChanged();
}
public boolean isShowCamera(){
return showCamera;
}
/**
* 选择某个图片,改变选择状态
* @param image
*/
public void select(Image image) {
if(mSelectedImages.contains(image)){
mSelectedImages.remove(image);
}else{
mSelectedImages.add(image);
}
notifyDataSetChanged();
}
/**
* 通过图片路径设置默认选择
* @param resultList
*/
public void setDefaultSelected(ArrayList<String> resultList) {
for(String path : resultList){
Image image = getImageByPath(path);
if(image != null){
mSelectedImages.add(image);
}
}
if(mSelectedImages.size() > 0){
notifyDataSetChanged();
}
}
private Image getImageByPath(String path){
if(mImages != null && mImages.size()>0){
for(Image image : mImages){
if(image.path.equalsIgnoreCase(path)){
return image;
}
}
}
return null;
}
/**
* 设置数据集
* @param images
*/
public void setData(List<Image> images) {
mSelectedImages.clear();
if(images != null && images.size()>0){
mImages = images;
}else{
mImages.clear();
}
notifyDataSetChanged();
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
if(showCamera){
return position==0 ? TYPE_CAMERA : TYPE_NORMAL;
}
return TYPE_NORMAL;
}
@Override
public int getCount() {
return showCamera ? mImages.size()+1 : mImages.size();
}
@Override
public Image getItem(int i) {
if(showCamera){
if(i == 0){
return null;
}
return mImages.get(i-1);
}else{
return mImages.get(i);
}
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
if(isShowCamera()){
if(i == 0){
view = mInflater.inflate(R.layout.mis_list_item_camera, viewGroup, false);
return view;
}
}
ViewHolder holder;
if(view == null){
view = mInflater.inflate(R.layout.mis_list_item_image, viewGroup, false);
holder = new ViewHolder(view);
}else{
holder = (ViewHolder) view.getTag();
}
if(holder != null) {
holder.bindData(getItem(i));
}
return view;
}
class ViewHolder {
ImageView image;
ImageView indicator;
View mask;
ViewHolder(View view){
image = (ImageView) view.findViewById(R.id.image);
indicator = (ImageView) view.findViewById(R.id.checkmark);
mask = view.findViewById(R.id.mask);
view.setTag(this);
}
void bindData(final Image data){
if(data == null) return;
// 处理单选和多选状态
if(showSelectIndicator){
indicator.setVisibility(View.VISIBLE);
if(mSelectedImages.contains(data)){
// 设置选中状态
indicator.setImageResource(R.drawable.mis_btn_selected);
mask.setVisibility(View.VISIBLE);
}else{
// 未选择
indicator.setImageResource(R.drawable.mis_btn_unselected);
mask.setVisibility(View.GONE);
}
}else{
indicator.setVisibility(View.GONE);
}
File imageFile = new File(data.path);
if (imageFile.exists()) {
// 显示图片
RequestOptions requestOptions = new RequestOptions()
.centerCrop()
.placeholder(R.drawable.mis_default_error)
.error(R.drawable.mis_default_error)
.override(mGridWidth, mGridWidth);
Glide.with(mContext ).asBitmap()
.load(imageFile)
.apply(requestOptions)
.into(image);
// Picasso.with(mContext)
// .load(imageFile)
// .placeholder(R.drawable.mis_default_error)
// .tag(MultiImageSelectorFragment.TAG)
// .resize(mGridWidth, mGridWidth)
// .centerCrop()
// .into(image);
}else{
image.setImageResource(R.drawable.mis_default_error);
}
}
}
}

View File

@@ -0,0 +1,27 @@
package com.fengliyan.uikit.photopicker.bean;
import android.text.TextUtils;
import java.util.List;
/**
* 文件夹
* Created by Nereo on 2015/4/7.
*/
public class Folder {
public String name;
public String path;
public Image cover;
public List<Image> images;
@Override
public boolean equals(Object o) {
try {
Folder other = (Folder) o;
return TextUtils.equals(other.path, path);
}catch (ClassCastException e){
e.printStackTrace();
}
return super.equals(o);
}
}

View File

@@ -0,0 +1,30 @@
package com.fengliyan.uikit.photopicker.bean;
import android.text.TextUtils;
/**
* 图片实体
* Created by Nereo on 2015/4/7.
*/
public class Image {
public String path;
public String name;
public long time;
public Image(String path, String name, long time){
this.path = path;
this.name = name;
this.time = time;
}
@Override
public boolean equals(Object o) {
try {
Image other = (Image) o;
return TextUtils.equals(this.path, other.path);
}catch (ClassCastException e){
e.printStackTrace();
}
return super.equals(o);
}
}

View File

@@ -0,0 +1,129 @@
package com.fengliyan.uikit.photopicker.utils;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.io.IOException;
import static android.os.Environment.MEDIA_MOUNTED;
/**
* 文件操作类
* Created by Nereo on 2015/4/8.
*/
public class FileUtils {
private static final String JPEG_FILE_PREFIX = "IMG_";
private static final String JPEG_FILE_SUFFIX = ".jpg";
public static File createTmpFile(Context context) throws IOException {
File dir = null;
if(TextUtils.equals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED)) {
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
if (!dir.exists()) {
dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera");
if (!dir.exists()) {
dir = getCacheDirectory(context, true);
}
}
}else{
dir = getCacheDirectory(context, true);
}
return File.createTempFile(JPEG_FILE_PREFIX, JPEG_FILE_SUFFIX, dir);
}
private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
/**
* Returns application cache directory. Cache directory will be created on SD card
* <i>("/Android/data/[app_package_name]/cache")</i> if card is mounted and app has appropriate permission. Else -
* Android defines cache directory on device's file system.
*
* @param context Application context
* @return Cache {@link File directory}.<br />
* <b>NOTE:</b> Can be null in some unpredictable cases (if SD card is unmounted and
* {@link Context#getCacheDir() Context.getCacheDir()} returns null).
*/
public static File getCacheDirectory(Context context) {
return getCacheDirectory(context, true);
}
/**
* Returns application cache directory. Cache directory will be created on SD card
* <i>("/Android/data/[app_package_name]/cache")</i> (if card is mounted and app has appropriate permission) or
* on device's file system depending incoming parameters.
*
* @param context Application context
* @param preferExternal Whether prefer external location for cache
* @return Cache {@link File directory}.<br />
* <b>NOTE:</b> Can be null in some unpredictable cases (if SD card is unmounted and
* {@link Context#getCacheDir() Context.getCacheDir()} returns null).
*/
public static File getCacheDirectory(Context context, boolean preferExternal) {
File appCacheDir = null;
String externalStorageState;
try {
externalStorageState = Environment.getExternalStorageState();
} catch (NullPointerException e) { // (sh)it happens (Issue #660)
externalStorageState = "";
} catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue #989)
externalStorageState = "";
}
if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {
appCacheDir = getExternalCacheDir(context);
}
if (appCacheDir == null) {
appCacheDir = context.getCacheDir();
}
if (appCacheDir == null) {
String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
appCacheDir = new File(cacheDirPath);
}
return appCacheDir;
}
/**
* Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be
* created on SD card <i>("/Android/data/[app_package_name]/cache/uil-images")</i> if card is mounted and app has
* appropriate permission. Else - Android defines cache directory on device's file system.
*
* @param context Application context
* @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images")
* @return Cache {@link File directory}
*/
public static File getIndividualCacheDirectory(Context context, String cacheDir) {
File appCacheDir = getCacheDirectory(context);
File individualCacheDir = new File(appCacheDir, cacheDir);
if (!individualCacheDir.exists()) {
if (!individualCacheDir.mkdir()) {
individualCacheDir = appCacheDir;
}
}
return individualCacheDir;
}
private static File getExternalCacheDir(Context context) {
File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
if (!appCacheDir.exists()) {
if (!appCacheDir.mkdirs()) {
return null;
}
try {
new File(appCacheDir, ".nomedia").createNewFile();
} catch (IOException e) {
}
}
return appCacheDir;
}
private static boolean hasExternalStoragePermission(Context context) {
int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
return perm == PackageManager.PERMISSION_GRANTED;
}
}

View File

@@ -0,0 +1,29 @@
package com.fengliyan.uikit.photopicker.utils;
import android.content.Context;
import android.graphics.Point;
import android.os.Build;
import android.view.Display;
import android.view.WindowManager;
/**
* 屏幕工具
* Created by nereo on 15/11/19.
* Updated by nereo on 2016/1/19.
*/
public class ScreenUtils {
public static Point getScreenSize(Context context){
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point out = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
display.getSize(out);
}else{
int width = display.getWidth();
int height = display.getHeight();
out.set(width, height);
}
return out;
}
}

View File

@@ -0,0 +1,31 @@
package com.fengliyan.uikit.photopicker.utils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* 时间处理工具
* Created by Nereo on 2015/4/8.
*/
public class TimeUtils {
public static String timeFormat(long timeMillis, String pattern){
SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.CHINA);
return format.format(new Date(timeMillis));
}
public static String formatPhotoDate(long time){
return timeFormat(time, "yyyy-MM-dd");
}
public static String formatPhotoDate(String path){
File file = new File(path);
if(file.exists()){
long time = file.lastModified();
return formatPhotoDate(time);
}
return "1970-01-01";
}
}

View File

@@ -0,0 +1,24 @@
package com.fengliyan.uikit.photopicker.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* Created by nereo on 15/11/10.
*/
public class SquareFrameLayout extends FrameLayout {
public SquareFrameLayout(Context context) {
super(context);
}
public SquareFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
}
}

View File

@@ -0,0 +1,22 @@
package com.fengliyan.uikit.photopicker.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
/** An image view which always remains square with respect to its width. */
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());
}
}

View File

@@ -0,0 +1,140 @@
package com.fengliyan.uikit.picker.citypicker;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.fengliyan.uikit.picker.citypicker.adapter.OnPickListener;
import com.fengliyan.uikit.picker.citypicker.model.HotCity;
import com.fengliyan.uikit.picker.citypicker.model.LocateState;
import com.fengliyan.uikit.picker.citypicker.model.LocatedCity;
import java.util.List;
/**
* @Author: Bro0cL
* @Date: 2018/2/6 17:52
*/
public class CityPicker {
private static final String TAG = "CityPicker";
private static CityPicker mInstance;
private CityPickerDialogFragment mCityPickerFragment;
private CityPicker(){}
public static CityPicker getInstance(){
if (mInstance == null){
synchronized (CityPicker.class){
if (mInstance == null){
mInstance = new CityPicker();
}
}
}
return mInstance;
}
private FragmentManager mFragmentManager;
private Fragment mTargetFragment;
private boolean enableAnim;
private int mAnimStyle;
private LocatedCity mLocation;
private List<HotCity> mHotCities;
private OnPickListener mOnPickListener;
public CityPicker setFragmentManager(FragmentManager fm) {
this.mFragmentManager = fm;
return this;
}
public CityPicker setTargetFragment(Fragment targetFragment) {
this.mTargetFragment = targetFragment;
return this;
}
/**
* 设置动画效果
* @param animStyle
* @return
*/
public CityPicker setAnimationStyle(@StyleRes int animStyle) {
this.mAnimStyle = animStyle;
return this;
}
/**
* 设置当前已经定位的城市
* @param location
* @return
*/
public CityPicker setLocatedCity(LocatedCity location) {
this.mLocation = location;
if (null != mCityPickerFragment) {
if (null != location) {
locateComplete(location, LocateState.SUCCESS);
}
}
return this;
}
public CityPicker setHotCities(List<HotCity> data){
this.mHotCities = data;
return this;
}
/**
* 启用动画效果默认为false
* @param enable
* @return
*/
public CityPicker enableAnimation(boolean enable){
this.enableAnim = enable;
return this;
}
/**
* 设置选择结果的监听器
* @param listener
* @return
*/
public CityPicker setOnPickListener(OnPickListener listener){
this.mOnPickListener = listener;
return this;
}
public void show(){
if (mFragmentManager == null){
throw new UnsupportedOperationException("CityPickermethod setFragmentManager() must be called.");
}
FragmentTransaction ft = mFragmentManager.beginTransaction();
final Fragment prev = mFragmentManager.findFragmentByTag(TAG);
if (prev != null){
ft.remove(prev).commit();
ft = mFragmentManager.beginTransaction();
}
ft.addToBackStack(null);
mCityPickerFragment = CityPickerDialogFragment.newInstance(enableAnim);
mCityPickerFragment.setLocatedCity(mLocation);
mCityPickerFragment.setHotCities(mHotCities);
mCityPickerFragment.setAnimationStyle(mAnimStyle);
mCityPickerFragment.setOnPickListener(mOnPickListener);
if (mTargetFragment != null){
mCityPickerFragment.setTargetFragment(mTargetFragment, 0);
}
mCityPickerFragment.show(ft, TAG);
}
/**
* 定位完成
* @param location
* @param state
*/
public void locateComplete(LocatedCity location, @LocateState.State int state){
CityPickerDialogFragment fragment = (CityPickerDialogFragment) mFragmentManager.findFragmentByTag(TAG);
if (fragment != null){
fragment.locationChanged(location, state);
}
}
}

View File

@@ -0,0 +1,275 @@
package com.fengliyan.uikit.picker.citypicker;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.picker.citypicker.adapter.CityListAdapter;
import com.fengliyan.uikit.picker.citypicker.adapter.InnerListener;
import com.fengliyan.uikit.picker.citypicker.adapter.OnPickListener;
import com.fengliyan.uikit.picker.citypicker.adapter.decoration.DividerItemDecoration;
import com.fengliyan.uikit.picker.citypicker.adapter.decoration.SectionItemDecoration;
import com.fengliyan.uikit.picker.citypicker.db.DBManager;
import com.fengliyan.uikit.picker.citypicker.model.City;
import com.fengliyan.uikit.picker.citypicker.model.HotCity;
import com.fengliyan.uikit.picker.citypicker.model.LocateState;
import com.fengliyan.uikit.picker.citypicker.model.LocatedCity;
import com.fengliyan.uikit.picker.citypicker.view.SideIndexBar;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Bro0cL
* @Date: 2018/2/6 20:50
*/
public class CityPickerDialogFragment extends AppCompatDialogFragment implements TextWatcher,
View.OnClickListener, SideIndexBar.OnIndexTouchedChangedListener, InnerListener {
private View mContentView;
private RecyclerView mRecyclerView;
private View mEmptyView;
private TextView mOverlayTextView;
private SideIndexBar mIndexBar;
private EditText mSearchBox;
private TextView mCancelBtn;
private ImageView mClearAllBtn;
private LinearLayoutManager mLayoutManager;
private CityListAdapter mAdapter;
private List<City> mAllCities;
private List<HotCity> mHotCities;
private List<City> mResults;
private DBManager dbManager;
private boolean enableAnim = false;
private int mAnimStyle = R.style.DefaultCityPickerAnimation;
private LocatedCity mLocatedCity;
private int locateState;
private OnPickListener mOnPickListener;
/**
* 获取实例
* @param enable 是否启用动画效果
* @return
*/
public static CityPickerDialogFragment newInstance(boolean enable){
final CityPickerDialogFragment fragment = new CityPickerDialogFragment();
Bundle args = new Bundle();
args.putBoolean("cp_enable_anim", enable);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, R.style.CityPickerStyle);
Bundle args = getArguments();
if (args != null) {
enableAnim = args.getBoolean("cp_enable_anim");
}
initHotCities();
initLocatedCity();
dbManager = new DBManager(getContext());
mAllCities = dbManager.getAllCities();
mAllCities.add(0, mLocatedCity);
//mAllCities.add(1, new HotCity("热门城市", "未知", "0"));
mResults = mAllCities;
}
private void initLocatedCity() {
if (mLocatedCity == null){
mLocatedCity = new LocatedCity(getString(R.string.cp_locating), "未知", "0");
locateState = LocateState.FAILURE;
}else{
if (!TextUtils.isEmpty(mLocatedCity.getName())) {
locateState = LocateState.SUCCESS;
}else {
mLocatedCity = new LocatedCity(getString(R.string.cp_locating), "未知", "0");
locateState = LocateState.FAILURE;
}
}
}
private void initHotCities() {
if (mHotCities == null || mHotCities.isEmpty()) {
mHotCities = new ArrayList<>();
mHotCities.add(new HotCity("北京", "北京", "101010100"));
mHotCities.add(new HotCity("上海", "上海", "101020100"));
mHotCities.add(new HotCity("广州", "广东", "101280101"));
mHotCities.add(new HotCity("深圳", "广东", "101280601"));
mHotCities.add(new HotCity("天津", "天津", "101030100"));
mHotCities.add(new HotCity("杭州", "浙江", "101210101"));
mHotCities.add(new HotCity("南京", "江苏", "101190101"));
mHotCities.add(new HotCity("成都", "四川", "101270101"));
mHotCities.add(new HotCity("武汉", "湖北", "101200101"));
}
}
public void setLocatedCity(LocatedCity location){
mLocatedCity = location;
}
public void setHotCities(List<HotCity> data){
if (data != null && !data.isEmpty()){
this.mHotCities = data;
}
}
@SuppressLint("ResourceType")
public void setAnimationStyle(@StyleRes int style){
this.mAnimStyle = style <= 0 ? R.style.DefaultCityPickerAnimation : style;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContentView = inflater.inflate(R.layout.cp_dialog_city_picker, container, false);
mRecyclerView = mContentView.findViewById(R.id.cp_city_recyclerview);
mLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addItemDecoration(new SectionItemDecoration(getActivity(), mAllCities), 0);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity()), 1);
mAdapter = new CityListAdapter(getActivity(), mAllCities, mHotCities, locateState);
mAdapter.setInnerListener(this);
mAdapter.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
//确保定位城市能正常刷新
if (newState == RecyclerView.SCROLL_STATE_IDLE){
mAdapter.refreshLocationItem();
}
}
});
mEmptyView = mContentView.findViewById(R.id.cp_empty_view);
mOverlayTextView = mContentView.findViewById(R.id.cp_overlay);
mIndexBar = mContentView.findViewById(R.id.cp_side_index_bar);
mIndexBar.setOverlayTextView(mOverlayTextView)
.setOnIndexChangedListener(this);
mSearchBox = mContentView.findViewById(R.id.cp_search_box);
mSearchBox.addTextChangedListener(this);
mCancelBtn = mContentView.findViewById(R.id.cp_cancel);
mClearAllBtn = mContentView.findViewById(R.id.cp_clear_all);
mCancelBtn.setOnClickListener(this);
mClearAllBtn.setOnClickListener(this);
return mContentView;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
Window window = dialog.getWindow();
if(window != null) {
window.getDecorView().setPadding(0, 0, 0, 0);
window.setBackgroundDrawableResource(android.R.color.transparent);
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
if (enableAnim) {
window.setWindowAnimations(mAnimStyle);
}
}
return dialog;
}
/** 搜索框监听 */
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
String keyword = s.toString();
if (TextUtils.isEmpty(keyword)){
mClearAllBtn.setVisibility(View.GONE);
mEmptyView.setVisibility(View.GONE);
mResults = mAllCities;
((SectionItemDecoration)(mRecyclerView.getItemDecorationAt(0))).setData(mResults);
mAdapter.updateData(mResults);
}else {
mClearAllBtn.setVisibility(View.VISIBLE);
//开始数据库查找
mResults = dbManager.searchCity(keyword);
((SectionItemDecoration)(mRecyclerView.getItemDecorationAt(0))).setData(mResults);
if (mResults == null || mResults.isEmpty()){
mEmptyView.setVisibility(View.VISIBLE);
}else {
mEmptyView.setVisibility(View.GONE);
mAdapter.updateData(mResults);
}
}
mRecyclerView.scrollToPosition(0);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.cp_cancel) {
dismiss(-1, null);
}else if(id == R.id.cp_clear_all){
mSearchBox.setText("");
}
}
@Override
public void onIndexChanged(String index, int position) {
//滚动RecyclerView到索引位置
mAdapter.scrollToSection(index);
}
public void locationChanged(LocatedCity location, int state){
mAdapter.updateLocateState(location, state);
}
@Override
public void dismiss(int position, City data) {
dismiss();
if (mOnPickListener != null){
mOnPickListener.onPick(position, data);
}
}
@Override
public void locate(){
if (mOnPickListener != null){
mOnPickListener.onLocate();
}
}
public void setOnPickListener(OnPickListener listener){
this.mOnPickListener = listener;
}
}

View File

@@ -0,0 +1,251 @@
package com.fengliyan.uikit.picker.citypicker.adapter;
import android.content.Context;
import android.os.Handler;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.picker.citypicker.adapter.decoration.GridItemDecoration;
import com.fengliyan.uikit.picker.citypicker.model.City;
import com.fengliyan.uikit.picker.citypicker.model.HotCity;
import com.fengliyan.uikit.picker.citypicker.model.LocateState;
import com.fengliyan.uikit.picker.citypicker.model.LocatedCity;
import java.util.List;
/**
* @Author: Bro0cL
* @Date: 2018/2/5 12:06
*/
public class CityListAdapter extends RecyclerView.Adapter<CityListAdapter.BaseViewHolder> {
private static final int VIEW_TYPE_CURRENT = 10;
private static final int VIEW_TYPE_HOT = 11;
private Context mContext;
private List<City> mData;
private List<HotCity> mHotData;
private int locateState;
private InnerListener mInnerListener;
private LinearLayoutManager mLayoutManager;
private boolean stateChanged;
public CityListAdapter(Context context, List<City> data, List<HotCity> hotData, int state) {
this.mData = data;
this.mContext = context;
this.mHotData = hotData;
this.locateState = state;
}
public void setLayoutManager(LinearLayoutManager manager){
this.mLayoutManager = manager;
}
public void updateData(List<City> data){
this.mData = data;
notifyDataSetChanged();
}
public void updateLocateState(LocatedCity location, int state){
mData.remove(0);
mData.add(0, location);
stateChanged = !(locateState == state);
locateState = state;
refreshLocationItem();
}
public void refreshLocationItem(){
//如果定位城市的item可见则进行刷新
if (stateChanged && mLayoutManager.findFirstVisibleItemPosition() == 0) {
stateChanged = false;
notifyItemChanged(0);
}
}
/**
* 滚动RecyclerView到索引位置
* @param index
*/
public void scrollToSection(String index){
if (mData == null || mData.isEmpty()) return;
if (TextUtils.isEmpty(index)) return;
int size = mData.size();
for (int i = 0; i < size; i++) {
if (TextUtils.equals(index.substring(0, 1), mData.get(i).getSection().substring(0, 1))){
if (mLayoutManager != null){
mLayoutManager.scrollToPositionWithOffset(i, 0);
if (TextUtils.equals(index.substring(0, 1), "")) {
//防止滚动时进行刷新
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (stateChanged) notifyItemChanged(0);
}
}, 1000);
}
return;
}
}
}
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType){
case VIEW_TYPE_CURRENT:
view = LayoutInflater.from(mContext).inflate(R.layout.cp_list_item_location_layout, parent, false);
return new LocationViewHolder(view);
/*case VIEW_TYPE_HOT:
view = LayoutInflater.from(mContext).inflate(R.layout.cp_list_item_hot_layout, parent, false);
return new HotViewHolder(view);*/
default:
view = LayoutInflater.from(mContext).inflate(R.layout.cp_list_item_default_layout, parent, false);
return new DefaultViewHolder(view);
}
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
if (holder == null) return;
if (holder instanceof DefaultViewHolder){
final int pos = holder.getAdapterPosition();
final City data = mData.get(pos);
if (data == null) return;
((DefaultViewHolder)holder).name.setText(data.getName());
((DefaultViewHolder) holder).name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mInnerListener != null){
mInnerListener.dismiss(pos, data);
}
}
});
}
//定位城市
if (holder instanceof LocationViewHolder){
final int pos = holder.getAdapterPosition();
final City data = mData.get(pos);
if (data == null) return;
//设置宽高
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
TypedValue typedValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.cpGridItemSpace, typedValue, true);
int space = mContext.getResources().getDimensionPixelSize(R.dimen.cp_grid_item_space);
int padding = mContext.getResources().getDimensionPixelSize(R.dimen.cp_default_padding);
int indexBarWidth = mContext.getResources().getDimensionPixelSize(R.dimen.cp_index_bar_width);
int itemWidth = (screenWidth - padding - space * (GridListAdapter.SPAN_COUNT - 1) - indexBarWidth) / GridListAdapter.SPAN_COUNT;
ViewGroup.LayoutParams lp = ((LocationViewHolder) holder).container.getLayoutParams();
lp.width = itemWidth;
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
((LocationViewHolder) holder).container.setLayoutParams(lp);
switch (locateState){
case LocateState.LOCATING:
((LocationViewHolder) holder).current.setText(R.string.cp_locating);
break;
case LocateState.SUCCESS:
((LocationViewHolder) holder).current.setText(data.getName());
break;
case LocateState.FAILURE:
((LocationViewHolder) holder).current.setText(R.string.cp_locate_failed);
break;
}
((LocationViewHolder) holder).container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (locateState == LocateState.SUCCESS) {
if (mInnerListener != null) {
mInnerListener.dismiss(pos, data);
}
} else if (locateState == LocateState.FAILURE){
// locateState = LocateState.LOCATING;
// notifyItemChanged(0);
if (mInnerListener != null){
mInnerListener.locate();
}
}
}
});
}
//热门城市
/*if (holder instanceof HotViewHolder){
final int pos = holder.getAdapterPosition();
final City data = mData.get(pos);
if (data == null) return;
GridListAdapter mAdapter = new GridListAdapter(mContext, mHotData);
mAdapter.setInnerListener(mInnerListener);
((HotViewHolder) holder).mRecyclerView.setAdapter(mAdapter);
}*/
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
@Override
public int getItemViewType(int position) {
if (position == 0 && TextUtils.equals("", mData.get(position).getSection().substring(0, 1)))
return VIEW_TYPE_CURRENT;
/*if (position == 1 && TextUtils.equals("热", mData.get(position).getSection().substring(0, 1)))
return VIEW_TYPE_HOT;*/
return super.getItemViewType(position);
}
public void setInnerListener(InnerListener listener){
this.mInnerListener = listener;
}
static class BaseViewHolder extends RecyclerView.ViewHolder{
BaseViewHolder(View itemView) {
super(itemView);
}
}
public static class DefaultViewHolder extends BaseViewHolder{
TextView name;
DefaultViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.cp_list_item_name);
}
}
public static class HotViewHolder extends BaseViewHolder {
RecyclerView mRecyclerView;
HotViewHolder(View itemView) {
super(itemView);
mRecyclerView = itemView.findViewById(R.id.cp_hot_list);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new GridLayoutManager(itemView.getContext(),
GridListAdapter.SPAN_COUNT, LinearLayoutManager.VERTICAL, false));
int space = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.cp_grid_item_space);
mRecyclerView.addItemDecoration(new GridItemDecoration(GridListAdapter.SPAN_COUNT,
space));
}
}
public static class LocationViewHolder extends BaseViewHolder {
FrameLayout container;
TextView current;
LocationViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.cp_list_item_location_layout);
current = itemView.findViewById(R.id.cp_list_item_location);
}
}
}

View File

@@ -0,0 +1,91 @@
package com.fengliyan.uikit.picker.citypicker.adapter;
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.picker.citypicker.model.City;
import com.fengliyan.uikit.picker.citypicker.model.HotCity;
import java.util.List;
/**
* @Author: Bro0cL
* @Date: 2018/2/8 21:22
*/
public class GridListAdapter extends RecyclerView.Adapter<GridListAdapter.GridViewHolder>{
public static final int SPAN_COUNT = 3;
private Context mContext;
private List<HotCity> mData;
private InnerListener mInnerListener;
public GridListAdapter(Context context, List<HotCity> data) {
this.mContext = context;
this.mData = data;
}
@Override
public GridViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.cp_grid_item_layout, parent, false);
return new GridViewHolder(view);
}
@Override
public void onBindViewHolder(GridViewHolder holder, int position) {
final int pos = holder.getAdapterPosition();
final City data = mData.get(pos);
if (data == null) return;
//设置item宽高
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
TypedValue typedValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.cpGridItemSpace, typedValue, true);
int space = mContext.getResources().getDimensionPixelSize(typedValue.resourceId);
int padding = mContext.getResources().getDimensionPixelSize(R.dimen.cp_default_padding);
int indexBarWidth = mContext.getResources().getDimensionPixelSize(R.dimen.cp_index_bar_width);
int itemWidth = (screenWidth - padding - space * (SPAN_COUNT - 1) - indexBarWidth) / SPAN_COUNT;
ViewGroup.LayoutParams lp = holder.container.getLayoutParams();
lp.width = itemWidth;
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
holder.container.setLayoutParams(lp);
holder.name.setText(data.getName());
holder.container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mInnerListener != null){
mInnerListener.dismiss(pos, data);
}
}
});
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class GridViewHolder extends RecyclerView.ViewHolder{
FrameLayout container;
TextView name;
public GridViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.cp_grid_item_layout);
name = itemView.findViewById(R.id.cp_gird_item_name);
}
}
public void setInnerListener(InnerListener listener){
this.mInnerListener = listener;
}
}

View File

@@ -0,0 +1,8 @@
package com.fengliyan.uikit.picker.citypicker.adapter;
import com.fengliyan.uikit.picker.citypicker.model.City;
public interface InnerListener {
void dismiss(int position, City data);
void locate();
}

View File

@@ -0,0 +1,9 @@
package com.fengliyan.uikit.picker.citypicker.adapter;
import com.fengliyan.uikit.picker.citypicker.model.City;
public interface OnPickListener {
void onPick(int position, City data);
void onLocate();
}

View File

@@ -0,0 +1,46 @@
package com.fengliyan.uikit.picker.citypicker.adapter.decoration;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.TypedValue;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import com.fengliyan.uikit.R;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private float dividerHeight;
private Paint mPaint;
public DividerItemDecoration(Context context) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.cpSectionBackground, typedValue, true);
mPaint.setColor(context.getResources().getColor(typedValue.resourceId));
dividerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.5f, context.getResources().getDisplayMetrics());
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = (int) dividerHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float bottom = view.getBottom() + dividerHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
}
}

View File

@@ -0,0 +1,28 @@
package com.fengliyan.uikit.picker.citypicker.adapter.decoration;
import android.graphics.Rect;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public class GridItemDecoration extends RecyclerView.ItemDecoration{
private int mSpanCount;
private int mSpace;
public GridItemDecoration(int spanCount, int space) {
this.mSpanCount = spanCount;
this.mSpace = space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % mSpanCount;
outRect.left = column * mSpace / mSpanCount;
outRect.right = mSpace - (column + 1) * mSpace / mSpanCount;
if (position >= mSpanCount) {
outRect.top = mSpace;
}
}
}

View File

@@ -0,0 +1,147 @@
package com.fengliyan.uikit.picker.citypicker.adapter.decoration;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.TypedValue;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.fengliyan.uikit.R;
import com.fengliyan.uikit.picker.citypicker.model.City;
import java.util.List;
public class SectionItemDecoration extends RecyclerView.ItemDecoration {
private List<City> mData;
private Paint mBgPaint;
private TextPaint mTextPaint;
private Rect mBounds;
private int mSectionHeight;
private int mBgColor;
private int mTextColor;
private int mTextSize;
public SectionItemDecoration(Context context, List<City> data) {
this.mData = data;
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.cpSectionBackground, typedValue, true);
mBgColor = context.getResources().getColor(typedValue.resourceId);
context.getTheme().resolveAttribute(R.attr.cpSectionHeight, typedValue, true);
mSectionHeight = context.getResources().getDimensionPixelSize(typedValue.resourceId);
context.getTheme().resolveAttribute(R.attr.cpSectionTextSize, typedValue, true);
mTextSize = context.getResources().getDimensionPixelSize(typedValue.resourceId);
context.getTheme().resolveAttribute(R.attr.cpSectionTextColor, typedValue, true);
mTextColor = context.getResources().getColor(typedValue.resourceId);
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setColor(mBgColor);
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mTextColor);
mBounds = new Rect();
}
public void setData(List<City> data) {
this.mData = data;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
int position = params.getViewLayoutPosition();
if (mData != null && !mData.isEmpty() && position <= mData.size() - 1 && position > -1) {
if (position == 0) {
drawSection(c, left, right, child, params, position);
} else {
if (null != mData.get(position).getSection()
&& !mData.get(position).getSection().equals(mData.get(position - 1).getSection())) {
drawSection(c, left, right, child, params, position);
}
}
}
}
}
private void drawSection(Canvas c, int left, int right, View child,
RecyclerView.LayoutParams params, int position) {
c.drawRect(left,
child.getTop() - params.topMargin - mSectionHeight,
right,
child.getTop() - params.topMargin, mBgPaint);
mTextPaint.getTextBounds(mData.get(position).getSection(),
0,
mData.get(position).getSection().length(),
mBounds);
c.drawText(mData.get(position).getSection(),
child.getPaddingLeft(),
child.getTop() - params.topMargin - (mSectionHeight / 2 - mBounds.height() / 2),
mTextPaint);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
if (pos < 0) return;
if (mData == null || mData.isEmpty()) return;
String section = mData.get(pos).getSection();
View child = parent.findViewHolderForLayoutPosition(pos).itemView;
boolean flag = false;
if ((pos + 1) < mData.size()) {
if (null != section && !section.equals(mData.get(pos + 1).getSection())) {
if (child.getHeight() + child.getTop() < mSectionHeight) {
c.save();
flag = true;
c.translate(0, child.getHeight() + child.getTop() - mSectionHeight);
}
}
}
c.drawRect(parent.getPaddingLeft(),
parent.getPaddingTop(),
parent.getRight() - parent.getPaddingRight(),
parent.getPaddingTop() + mSectionHeight, mBgPaint);
mTextPaint.getTextBounds(section, 0, section.length(), mBounds);
c.drawText(section,
child.getPaddingLeft(),
parent.getPaddingTop() + mSectionHeight - (mSectionHeight / 2 - mBounds.height() / 2),
mTextPaint);
if (flag)
c.restore();
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (mData != null && !mData.isEmpty() && position <= mData.size() - 1 && position > -1) {
if (position == 0) {
outRect.set(0, mSectionHeight, 0, 0);
} else {
if (null != mData.get(position).getSection()
&& !mData.get(position).getSection().equals(mData.get(position - 1).getSection())) {
outRect.set(0, mSectionHeight, 0, 0);
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
package com.fengliyan.uikit.picker.citypicker.db;
public class DBConfig {
public static final String DB_NAME_V1 = "china_cities_v2.db";
public static final String DB_NAME_V2 = "china_cities_v3.db";
public static final String LATEST_DB_NAME = DB_NAME_V2;
public static final String TABLE_NAME = "cities";
public static final String COLUMN_C_NAME = "c_name";
public static final String COLUMN_C_PROVINCE = "c_province";
public static final String COLUMN_C_PINYIN = "c_pinyin";
public static final String COLUMN_C_CODE = "c_code";
}

View File

@@ -0,0 +1,142 @@
package com.fengliyan.uikit.picker.citypicker.db;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import com.fengliyan.uikit.picker.citypicker.model.City;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.COLUMN_C_CODE;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.COLUMN_C_NAME;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.COLUMN_C_PINYIN;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.COLUMN_C_PROVINCE;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.DB_NAME_V1;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.LATEST_DB_NAME;
import static com.fengliyan.uikit.picker.citypicker.db.DBConfig.TABLE_NAME;
/**
* Author Bro0cL on 2016/1/26.
*/
public class DBManager {
private static final int BUFFER_SIZE = 1024;
private String DB_PATH;
private Context mContext;
public DBManager(Context context) {
this.mContext = context;
DB_PATH = File.separator + "data"
+ Environment.getDataDirectory().getAbsolutePath() + File.separator
+ context.getPackageName() + File.separator + "databases" + File.separator;
copyDBFile();
}
private void copyDBFile(){
File dir = new File(DB_PATH);
if (!dir.exists()){
dir.mkdirs();
}
//如果旧版数据库存在,则删除
File dbV1 = new File(DB_PATH + DB_NAME_V1);
if (dbV1.exists()){
dbV1.delete();
}
//创建新版本数据库
File dbFile = new File(DB_PATH + LATEST_DB_NAME);
if (!dbFile.exists()){
InputStream is;
OutputStream os;
try {
is = mContext.getResources().getAssets().open(LATEST_DB_NAME);
os = new FileOutputStream(dbFile);
byte[] buffer = new byte[BUFFER_SIZE];
int length;
while ((length = is.read(buffer, 0, buffer.length)) > 0){
os.write(buffer, 0, length);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public List<City> getAllCities(){
List<City> result = new ArrayList<>();
try {
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + LATEST_DB_NAME, null);
Cursor cursor = db.rawQuery("select * from " + TABLE_NAME, null);
City city;
while (cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex(COLUMN_C_NAME));
String province = cursor.getString(cursor.getColumnIndex(COLUMN_C_PROVINCE));
String pinyin = cursor.getString(cursor.getColumnIndex(COLUMN_C_PINYIN));
String code = cursor.getString(cursor.getColumnIndex(COLUMN_C_CODE));
city = new City(name, province, pinyin, code);
result.add(city);
}
cursor.close();
db.close();
Collections.sort(result, new CityComparator());
}catch (Exception e) {
}
return result;
}
public List<City> searchCity(final String keyword){
List<City> result = new ArrayList<>();
try {
String sql = "select * from " + TABLE_NAME + " where "
+ COLUMN_C_NAME + " like ? " + "or "
+ COLUMN_C_PINYIN + " like ? ";
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + LATEST_DB_NAME, null);
Cursor cursor = db.rawQuery(sql, new String[]{"%"+keyword+"%", keyword+"%"});
while (cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex(COLUMN_C_NAME));
String province = cursor.getString(cursor.getColumnIndex(COLUMN_C_PROVINCE));
String pinyin = cursor.getString(cursor.getColumnIndex(COLUMN_C_PINYIN));
String code = cursor.getString(cursor.getColumnIndex(COLUMN_C_CODE));
City city = new City(name, province, pinyin, code);
result.add(city);
}
cursor.close();
db.close();
CityComparator comparator = new CityComparator();
Collections.sort(result, comparator);
}catch (Exception e) {
}
return result;
}
/**
* sort by a-z
*/
private class CityComparator implements Comparator<City>{
@Override
public int compare(City lhs, City rhs) {
String a = lhs.getPinyin().substring(0, 1);
String b = rhs.getPinyin().substring(0, 1);
return a.compareTo(b);
}
}
}

View File

@@ -0,0 +1,77 @@
package com.fengliyan.uikit.picker.citypicker.model;
import android.text.TextUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* author Bro0cL on 2016/1/26.
*/
public class City {
private String name;
private String province;
private String pinyin;
private String code;
public City(String name, String province, String pinyin, String code) {
this.name = name;
this.province = province;
this.pinyin = pinyin;
this.code = code;
}
/***
* 获取悬浮栏文本,(#、定位、热门 需要特殊处理)
* @return
*/
public String getSection(){
if (TextUtils.isEmpty(pinyin)) {
return "#";
} else {
String c = pinyin.substring(0, 1);
Pattern p = Pattern.compile("[a-zA-Z]");
Matcher m = p.matcher(c);
if (m.matches()) {
return c.toUpperCase();
}
//在添加定位和热门数据时设置的section就是开头
else if (TextUtils.equals(c, "") || TextUtils.equals(c, ""))
return pinyin;
else
return "#";
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}

View File

@@ -0,0 +1,8 @@
package com.fengliyan.uikit.picker.citypicker.model;
public class HotCity extends City {
public HotCity(String name, String province, String code) {
super(name, province, "热门城市", code);
}
}

View File

@@ -0,0 +1,17 @@
package com.fengliyan.uikit.picker.citypicker.model;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class LocateState {
public static final int LOCATING = 123;
public static final int SUCCESS = 132;
public static final int FAILURE = 321;
@IntDef({SUCCESS, FAILURE})
@Retention(RetentionPolicy.SOURCE)
public @interface State{}
}

View File

@@ -0,0 +1,8 @@
package com.fengliyan.uikit.picker.citypicker.model;
public class LocatedCity extends City {
public LocatedCity(String name, String province, String code) {
super(name, province, "定位城市", code);
}
}

View File

@@ -0,0 +1,160 @@
package com.fengliyan.uikit.picker.citypicker.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.fengliyan.uikit.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author: Bro0cL
* @Date: 2018/2/8 10:56
*/
public class SideIndexBar extends View{
private static final String[] DEFAULT_INDEX_ITEMS = {"定位", /*"热门",*/ "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};
private List<String> mIndexItems;
private float mItemHeight; //每个index的高度
private int mTextSize; //sp
private int mTextColor;
private int mTextTouchedColor;
private int mCurrentIndex = -1;
private Paint mPaint;
private Paint mTouchedPaint;
private int mWidth;
private int mHeight;
private float mTopMargin; //居中绘制,文字绘制起点和控件顶部的间隔
private TextView mOverlayTextView;
private OnIndexTouchedChangedListener mOnIndexChangedListener;
public SideIndexBar(Context context) {
this(context, null);
}
public SideIndexBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SideIndexBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mIndexItems = new ArrayList<>();
mIndexItems.addAll(Arrays.asList(DEFAULT_INDEX_ITEMS));
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.cpIndexBarTextSize, typedValue, true);
mTextSize = context.getResources().getDimensionPixelSize(typedValue.resourceId);
context.getTheme().resolveAttribute(R.attr.cpIndexBarNormalTextColor, typedValue, true);
mTextColor = context.getResources().getColor(typedValue.resourceId);
context.getTheme().resolveAttribute(R.attr.cpIndexBarSelectedTextColor, typedValue, true);
mTextTouchedColor = context.getResources().getColor(typedValue.resourceId);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
mTouchedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTouchedPaint.setTextSize(mTextSize);
mTouchedPaint.setColor(mTextTouchedColor);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String index;
for (int i = 0; i < mIndexItems.size(); i++) {
index = mIndexItems.get(i);
Paint.FontMetrics fm = mPaint.getFontMetrics();
canvas.drawText(index,
(mWidth - mPaint.measureText(index)) / 2,
mItemHeight / 2 + (fm.bottom-fm.top) / 2 - fm.bottom + mItemHeight * i + mTopMargin,
i == mCurrentIndex ? mTouchedPaint : mPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();
mHeight = Math.max(h, oldh);
mItemHeight = mHeight / mIndexItems.size();
mTopMargin = (mHeight - mItemHeight * mIndexItems.size()) / 2;
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
performClick();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float y = event.getY();
int indexSize = mIndexItems.size();
int touchedIndex = (int) (y / mItemHeight);
if (touchedIndex < 0) {
touchedIndex = 0;
}else if (touchedIndex >= indexSize) {
touchedIndex = indexSize - 1;
}
if (mOnIndexChangedListener != null && touchedIndex >= 0 && touchedIndex < indexSize){
if (touchedIndex != mCurrentIndex) {
mCurrentIndex = touchedIndex;
if (mOverlayTextView != null){
mOverlayTextView.setVisibility(VISIBLE);
mOverlayTextView.setText(mIndexItems.get(touchedIndex));
}
mOnIndexChangedListener.onIndexChanged(mIndexItems.get(touchedIndex), touchedIndex);
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mCurrentIndex = -1;
if (mOverlayTextView != null){
mOverlayTextView.setVisibility(GONE);
}
invalidate();
break;
}
return true;
}
public SideIndexBar setOverlayTextView(TextView overlay){
this.mOverlayTextView = overlay;
return this;
}
public SideIndexBar setOnIndexChangedListener(OnIndexTouchedChangedListener listener){
this.mOnIndexChangedListener = listener;
return this;
}
public interface OnIndexTouchedChangedListener{
void onIndexChanged(String index, int position);
}
}

View File

@@ -0,0 +1,142 @@
package com.fengliyan.uikit.refresh;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.fengliyan.uikit.R;
/**
* Created by abby on 2017/1/7.
*/
public class LoadMoreListView extends ListView {
private TextView mLoadMoreHint;
private View mLoadMoreView;
private View mEmptyView;
private OnDataLoadingListener mListener;
private boolean hasScrollView;
public interface OnDataLoadingListener {
void loading();
}
public void setOnDataLoadingListener(OnDataLoadingListener listener) {
mListener = listener;
}
public LoadMoreListView(Context context) {
super(context);
initView();
}
public LoadMoreListView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadMoreListView);
hasScrollView = a.getBoolean(R.styleable.LoadMoreListView_has_scroview, false);
initView();
}
public LoadMoreListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadMoreListView);
hasScrollView = a.getBoolean(R.styleable.LoadMoreListView_has_scroview, false);
initView();
}
public void setScollView(boolean hasScrollView) {
this.hasScrollView = hasScrollView;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = 0;
if (hasScrollView) {
// 设置最大高度(例如屏幕高度的 80%
int maxHeight = (int) (getResources().getDisplayMetrics().heightPixels * 0.3f);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specSize > maxHeight) {
expandSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
} else {
expandSpec = MeasureSpec.makeMeasureSpec(specSize, MeasureSpec.AT_MOST);
}
} else {
expandSpec = heightMeasureSpec;
}
super.onMeasure(widthMeasureSpec, expandSpec);
}
public void initView() {
mLoadMoreView = LayoutInflater.from(getContext()).inflate(R.layout.load_more_hint, null);
mLoadMoreHint = (TextView) mLoadMoreView.findViewById(R.id.load_more_text);
addFooterView(mLoadMoreView);
mLoadMoreView.setVisibility(View.GONE);
}
public void onDataReady() {
mLoadMoreView.setVisibility(View.GONE);
}
public void onDataLoading() {
mLoadMoreView.setVisibility(View.VISIBLE);
mLoadMoreHint.setText("正在加载更多...");
if (null != mListener) {
mListener.loading();
}
}
public void onDataLoading(String message) {
mLoadMoreView.setVisibility(View.VISIBLE);
mLoadMoreHint.setText(message);
if (null != mListener) {
mListener.loading();
}
}
public void onNoDataLoaded() {
mLoadMoreView.setVisibility(View.VISIBLE);
mLoadMoreHint.setText("我的陪伴 · 让生活更精彩");
}
public void onNoData() {
mLoadMoreView.setVisibility(View.VISIBLE);
mLoadMoreHint.setText("暂无数据 ~ ~ ");
}
public void addLoadMoreView(View v) {
removeFooterView(mLoadMoreView);
addFooterView(v);
mLoadMoreView = v;
}
public View getLoadMoreView() {
return mLoadMoreView;
}
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
// mEmptyView = LayoutInflater.from(getContext()).inflate(R.layout.empty_view, null);
//ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
//mEmptyView.setLayoutParams(params);
//((ViewGroup)super.getParent()).addView(mEmptyView);
//super.setEmptyView(mEmptyView);
}
public void dimissEmptyView() {
if (null != mEmptyView) {
mEmptyView.setVisibility(GONE);
}
}
public void colorLoadMoreViewByResId(int resId) {
mLoadMoreView.setBackgroundResource(resId);
}
}

View File

@@ -0,0 +1,63 @@
package com.fengliyan.uikit.scroll;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/**
* Created by chenqihong on 2017/3/9.
*/
public class ObservedScrollView extends ScrollView {
private ScrollViewListener mScrollViewListener = null;
public interface ScrollViewListener{
void onScrollChanged(View scrollView, int x, int y, int oldx, int oldy);
void onBottom();
void onTop();
}
public ObservedScrollView(Context context) {
super(context);
}
public ObservedScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public ObservedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.mScrollViewListener = scrollViewListener;
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (mScrollViewListener != null) {
mScrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (scrollY == 0) {
if(clampedY){
if (mScrollViewListener != null) {
mScrollViewListener.onTop();
}
}
} else {
if(clampedY){
if (mScrollViewListener != null) {
mScrollViewListener.onBottom();
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More