Compare commits

..

2 Commits

Author SHA1 Message Date
gongduoxiang
a3b4fe3d03 动态加载so,稍微修改了一下 2024-08-10 16:27:59 +08:00
gongduoxiang
1f66204031 动态加载so文件方案实现 2024-07-30 13:39:37 +08:00
546 changed files with 11356 additions and 27826 deletions

View File

@@ -334,7 +334,4 @@
<string name="home_function_name_fine_sticker">Exquisite sticker</string>
<string name="dialog_reset">Reset</string>
<string name="menu_diy">Custom</string>
<string name="toast_not_detect_face">No face tracking</string>
<string name="toast_not_detect_face_or_body">No face or body tracking</string>
</resources>

View File

@@ -31,6 +31,4 @@ repositories {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'com.google.code.gson:gson:2.8.6'
}

View File

@@ -1,134 +0,0 @@
package com.samsung.android.sdk.iap.lib;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import com.samsung.android.sdk.iap.lib.constants.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
import com.samsung.android.sdk.iap.lib.vo.PurchaseVo;
import java.util.ArrayList;
public class SamsungUtil {
private Context mContext;
IapHelper iapHelper;
private static SamsungUtil samsungUtil;
public static SamsungUtil newInstance(Context context) {
if (samsungUtil == null) {
samsungUtil = new SamsungUtil(context);
}
return samsungUtil;
}
public SamsungUtil(Context mContext) {
this.mContext = mContext;
}
/**
* 初始化
*/
public void init() {
iapHelper = IapHelper.getInstance(mContext);
//设置支付模式 OPERATION_MODE_PRODUCTION 正式模式 OPERATION_MODE_TEST 测试模式
iapHelper.setOperationMode(HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION);
}
public void dispose() {
if (iapHelper != null) {
iapHelper.dispose();
}
}
/**
* 购买
*
* @param skuId
*/
public void buy(String skuId, OnPaymentListener onPaymentListener) {
//购买
iapHelper.startPayment(skuId, "", (errorVo, purchaseVo) -> {
if (purchaseVo != null) {
onPaymentListener.onPaymentSuccess(purchaseVo.getPurchaseId());
} else {
if (errorVo.getErrorCode() == HelperDefine.IAP_PAYMENT_IS_CANCELED) {
onPaymentListener.onPaymentFailed(mContext.getString(R.string.pay_cancel));
} else {
onPaymentListener.onPaymentFailed(errorVo.getErrorString());
}
}
});
}
public interface OnPaymentListener {
void onPaymentSuccess(String purchaseVo);
void onPaymentFailed(String errorVo);
}
/**
* 消耗指定商品
*
* @param skuId
*/
public void consume(String skuId) {
//消耗
iapHelper.consumePurchasedItems(skuId, new OnConsumePurchasedItemsListener() {
@Override
public void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList) {
if (_consumeList != null) {
Log.e("samsung","消耗:" + new Gson().toJson(_consumeList));
Log.e("samsung","ErrorVo" + _errorVO.dump());
}
}
});
}
public void consumeAll(ArrayList<OwnedProductVo> _ownedList) {
if (_ownedList.size() > 0) {
iapHelper.consumePurchasedItems(_ownedList.get(0).getPurchaseId(), new OnConsumePurchasedItemsListener() {
@Override
public void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList1) {
if (_errorVO.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
Log.e("samsung","消耗:" + new Gson().toJson(_consumeList1));
Log.e("samsung","ErrorVo" + _errorVO.dump());
_ownedList.remove(0);
consumeAll(_ownedList);
}
}
});
}
}
/**
* 消耗所有未消耗的消耗品
*/
public void query() {
//查询商品 PRODUCT_TYPE_ITEM 消耗品&非消耗品
iapHelper.getOwnedList(HelperDefine.PRODUCT_TYPE_ITEM, new OnGetOwnedListListener() {
@Override
public void onGetOwnedProducts(ErrorVo _errorVo, ArrayList<OwnedProductVo> _ownedList) {
if (_errorVo != null) {
if (_errorVo.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
if (_ownedList != null) {
consumeAll(_ownedList);
}
}
}
}
});
}
}

View File

@@ -0,0 +1,85 @@
package com.samsung.android.sdk.iap.lib.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
/**
* Created by sangbum7.kim on 2018-03-06.
*/
public class AccountActivity extends Activity {
private static final String TAG = AccountActivity.class.getSimpleName();
IapHelper mIapHelper = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIapHelper = IapHelper.getInstance(this);
// ====================================================================
// 1. If IAP package is installed and valid, start SamsungAccount
// authentication activity to start purchase.
// ====================================================================
Log.i(TAG, "Samsung Account Login...");
HelperUtil.startAccountActivity(this);
// ====================================================================
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected void onActivityResult(int _requestCode, int _resultCode, Intent intent) {
Log.i(TAG, "onActivityResult>> requestCode : " + _requestCode + ", resultCode : " + _resultCode);
switch (_requestCode) {
case HelperDefine.REQUEST_CODE_IS_ACCOUNT_CERTIFICATION:
Log.i(TAG, "REQUEST_CODE_IS_ACCOUNT_CERTIFICATION Result : " + _resultCode);
// 1) If SamsungAccount authentication is succeed
// ------------------------------------------------------------
if (RESULT_OK == _resultCode) {
// bind to IAPService
// --------------------------------------------------------
Runnable runProcess = new Runnable() {
@Override
public void run() {
mIapHelper.bindIapService();
}
};
runProcess.run();
finish();
overridePendingTransition(0, 0);
// --------------------------------------------------------
}
// ------------------------------------------------------------
// 2) If SamsungAccount authentication is cancelled
// ------------------------------------------------------------
else {
// Runnable runProcess = new Runnable() {
// @Override
// public void run() {
// if(mIapHelper.getServiceProcess() != null)
// mIapHelper.getServiceProcess().onEndProcess();
// else
// mIapHelper.dispose();
// }
// };
// if(runProcess!=null)
// runProcess.run();
// else
mIapHelper.dispose();
finish();
}
break;
}
}
}

View File

@@ -0,0 +1,233 @@
package com.samsung.android.sdk.iap.lib.activity;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.dialog.BaseDialogFragment;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import com.samsung.android.sdk.iap.lib.vo.PurchaseVo;
public abstract class BaseActivity extends Activity {
private static final String TAG = BaseActivity.class.getSimpleName();
protected ErrorVo mErrorVo = new ErrorVo();
private Dialog mProgressDialog = null;
protected PurchaseVo mPurchaseVo = null;
/**
* Helper Class between IAPService and 3rd Party Application
*/
IapHelper mIapHelper = null;
/**
* Flag value to show successful pop-up. Error pop-up appears whenever it fails or not.
*/
protected boolean mShowErrorDialog = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 1. IapHelper Instance creation
// To test on development, set mode to test mode using
// use HelperDefine.OperationMode.OPERATION_MODE_TEST or
// HelperDefine.OperationMode.OPERATION_MODE_TEST_FAILURE constants.
// ====================================================================
mIapHelper = IapHelper.getInstance(this);
// ====================================================================
// 2. This activity is invisible excepting progress bar as default.
// ====================================================================
try {
Toast.makeText(this,
R.string.dream_sapps_body_authenticating_ing,
Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
// ====================================================================
super.onCreate(savedInstanceState);
}
public void setErrorVo(ErrorVo _errorVo) {
mErrorVo = _errorVo;
}
public boolean checkAppsPackage(Activity _activity) {
// 1. If Galaxy Store is installed
// ====================================================================
if (HelperUtil.isInstalledAppsPackage(this)) {
// 1) If Galaxy Store is enabled
// ================================================================
if (!HelperUtil.isEnabledAppsPackage(this)) {
HelperUtil.showEnableGalaxyStoreDialog(_activity);
// ================================================================
// 2) If Galaxy Store is valid
// ================================================================
} else if (HelperUtil.isValidAppsPackage(this)) {
return true;
} else {
// Set error to notify result to third-party application
// ------------------------------------------------------------
final String ERROR_ISSUER_IAP_CLIENT = "IC";
final int ERROR_CODE_INVALID_GALAXY_STORE = 10002;
String errorString = String.format(
getString(
R.string.dream_ph_body_contact_p1sscustomer_servicep2ss_for_more_information_n_nerror_code_c_p3ss),
"", "", ERROR_ISSUER_IAP_CLIENT + ERROR_CODE_INVALID_GALAXY_STORE);
mErrorVo.setError(HelperDefine.IAP_PAYMENT_IS_CANCELED, errorString);
HelperUtil.showInvalidGalaxyStoreDialog(this);
}
// ================================================================
// ====================================================================
// 2. If Galaxy Store is not installed
// ====================================================================
} else {
HelperUtil.installAppsPackage(this);
}
// ====================================================================
return false;
}
/**
* dispose IapHelper {@link PaymentActivity} To do that, preDestory must be invoked at first in onDestory of each child activity
*/
protected void preDestory() {
// 1. Invoke dispose Method to unbind service and release inprogress flag
// ====================================================================
if (mIapHelper != null) {
mIapHelper.dispose();
mIapHelper = null;
}
}
@Override
protected void onDestroy() {
// 1. dismiss ProgressDialog
// ====================================================================
try {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
} catch (Exception e) {
e.printStackTrace();
}
// ====================================================================
super.onDestroy();
}
@Override
public void finish() {
super.finish();
overridePendingTransition(0, 0);
}
protected void finishPurchase(Intent _intent) {
// 1. If there is bundle passed from IAP
// ====================================================================
if (_intent != null && _intent.getExtras() != null) {
Bundle extras = _intent.getExtras();
mErrorVo.setError(
extras.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
extras.getString(HelperDefine.KEY_NAME_ERROR_STRING),
extras.getString(HelperDefine.KEY_NAME_ERROR_DETAILS, ""));
// 1) If the purchase is successful,
// ----------------------------------------------------------------
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
mPurchaseVo = new PurchaseVo(extras.getString(
HelperDefine.KEY_NAME_RESULT_OBJECT));
mErrorVo.setError(
HelperDefine.IAP_ERROR_NONE,
getString(R.string.dream_sapps_body_your_purchase_is_complete));
finish();
}
// ----------------------------------------------------------------
// 2) If the purchase is failed
// ----------------------------------------------------------------
else {
Log.e(TAG, "finishPurchase: " + mErrorVo.dump());
if (mShowErrorDialog) {
HelperUtil.showIapErrorDialog(
this,
getString(R.string.dream_ph_pheader_couldnt_complete_purchase),
mErrorVo.getErrorString(),
mErrorVo.getErrorDetailsString(),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
finish();
}
},
null);
} else {
finish();
}
}
// ----------------------------------------------------------------
}
// ====================================================================
// 2. If there is no bundle passed from IAP
// ====================================================================
else {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
getString(R.string.mids_sapps_pop_unknown_error_occurred));
if (mShowErrorDialog) {
HelperUtil.showIapErrorDialog(
this,
getString(R.string.dream_ph_pheader_couldnt_complete_purchase),
getString(R.string.mids_sapps_pop_unknown_error_occurred),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
finish();
}
},
null);
} else {
finish();
}
return;
}
// ====================================================================
}
protected void cancelPurchase(Intent intent) {
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
mErrorVo.setError(
extras.getInt(
HelperDefine.KEY_NAME_STATUS_CODE,
HelperDefine.IAP_PAYMENT_IS_CANCELED),
extras.getString(
HelperDefine.KEY_NAME_ERROR_STRING,
getString(R.string.mids_sapps_pop_payment_canceled)),
extras.getString(HelperDefine.KEY_NAME_ERROR_DETAILS, ""));
finish();
return;
}
}
mErrorVo.setError(
HelperDefine.IAP_PAYMENT_IS_CANCELED,
getString(R.string.mids_sapps_pop_payment_canceled));
finish();
}
}

View File

@@ -0,0 +1,57 @@
package com.samsung.android.sdk.iap.lib.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
/**
* Created by sangbum7.kim on 2018-03-07.
*/
public class CheckPackageActivity extends Activity {
private static final String TAG = CheckPackageActivity.class.getSimpleName();
private boolean mFinishFlag = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFinishFlag = true;
Intent intent = getIntent();
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
int DialogType = extras.getInt("DialogType");
switch (DialogType) {
case HelperDefine.DIALOG_TYPE_INVALID_PACKAGE: {
HelperUtil.showInvalidGalaxyStoreDialog(this);
mFinishFlag = false;
}
break;
case HelperDefine.DIALOG_TYPE_DISABLE_APPLICATION: {
HelperUtil.showEnableGalaxyStoreDialog(this);
mFinishFlag = false;
}
break;
case HelperDefine.DIALOG_TYPE_APPS_DETAIL: {
HelperUtil.showUpdateGalaxyStoreDialog(this);
mFinishFlag = false;
}
break;
}
}
}
if (mFinishFlag) {
finish();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
IapHelper.getInstance(getApplicationContext()).dispose();
}
}

View File

@@ -0,0 +1,53 @@
package com.samsung.android.sdk.iap.lib.activity;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.dialog.BaseDialogFragment;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
/**
* Created by sangbum7.kim on 2018-03-05.
*/
public class DialogActivity extends Activity {
private static final String TAG = DialogActivity.class.getSimpleName();
private String mExtraString = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
if (HelperDefine.DIALOG_TYPE_NOTIFICATION == extras.getInt("DialogType")) {
String title = extras.getString("Title");
String message = extras.getString("Message");
HelperUtil.showIapErrorDialog(
this,
title,
message,
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
finish();
}
},
null);
}
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}

View File

@@ -0,0 +1,127 @@
package com.samsung.android.sdk.iap.lib.activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.HelperListenerManager;
import com.samsung.android.sdk.iap.lib.listener.OnPaymentListener;
public class PaymentActivity extends BaseActivity {
private static final String TAG = PaymentActivity.class.getSimpleName();
private String mItemId = null;
private String mPassThroughParam = "";
private int mMode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null
&& intent.getExtras().containsKey("ItemId")) {
Bundle extras = intent.getExtras();
mItemId = extras.getString("ItemId");
mPassThroughParam = extras.getString("PassThroughParam");
mShowErrorDialog = extras.getBoolean("ShowErrorDialog", true);
mMode = extras.getInt("OperationMode", HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION.getValue());
} else {
Toast.makeText(this,
R.string.mids_sapps_pop_an_invalid_value_has_been_provided_for_samsung_in_app_purchase,
Toast.LENGTH_LONG).show();
// Set error to pass result to third-party application
// ----------------------------------------------------------------
mErrorVo.setError(HelperDefine.IAP_ERROR_COMMON,
getString(R.string.mids_sapps_pop_an_invalid_value_has_been_provided_for_samsung_in_app_purchase));
// ----------------------------------------------------------------
finish();
}
if (checkAppsPackage(this)) {
startPaymentActivity();
}
}
@Override
protected void onDestroy() {
super.preDestory();
if (isFinishing()) {
OnPaymentListener onPaymentListener =
HelperListenerManager.getInstance().getOnPaymentListener();
HelperListenerManager.getInstance().setOnPaymentListener(null);
if (null != onPaymentListener) {
onPaymentListener.onPayment(mErrorVo, mPurchaseVo);
}
}
super.onDestroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
protected void onActivityResult(int _requestCode, int _resultCode, Intent _intent) {
switch (_requestCode) {
case HelperDefine.REQUEST_CODE_IS_IAP_PAYMENT: {
if (_resultCode == RESULT_OK) {
finishPurchase(_intent);
}
else if (_resultCode == RESULT_CANCELED) {
Log.e(TAG, "Payment is canceled.");
cancelPurchase(_intent);
}
break;
}
case HelperDefine.REQUEST_CODE_IS_ENABLE_APPS: {
if (checkAppsPackage(this)) {
startPaymentActivity();
}
break;
}
}
}
private void startPaymentActivity() {
if (mIapHelper == null) {
Log.e(TAG, "Fail to get IAP Helper instance");
return;
}
try {
Context context = this.getApplicationContext();
Bundle bundle = new Bundle();
bundle.putString(HelperDefine.KEY_NAME_THIRD_PARTY_NAME, context.getPackageName());
bundle.putString(HelperDefine.KEY_NAME_ITEM_ID, mItemId);
if (mPassThroughParam != null) {
bundle.putString(HelperDefine.KEY_NAME_PASSTHROUGH_ID, mPassThroughParam);
}
bundle.putInt(HelperDefine.KEY_NAME_OPERATION_MODE, mMode);
bundle.putString(HelperDefine.KEY_NAME_VERSION_CODE, HelperDefine.HELPER_VERSION);
ComponentName com = new ComponentName(HelperDefine.GALAXY_PACKAGE_NAME,
HelperDefine.IAP_PACKAGE_NAME + ".activity.PaymentMethodListActivity");
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(com);
intent.putExtras(bundle);
if (intent.resolveActivity(context.getPackageManager()) != null) {
startActivityForResult(intent, HelperDefine.REQUEST_CODE_IS_IAP_PAYMENT);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,213 @@
package com.samsung.android.sdk.iap.lib.dialog;
import android.app.ActionBar;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import com.samsung.android.sdk.iap.lib.R;
public class BaseDialogFragment extends DialogFragment implements View.OnClickListener {
private static final String TAG = BaseDialogFragment.class.getSimpleName();
private int dialogWidth;
private String title;
private CharSequence message;
private String messageExtra = "";
private String positiveBtnText;
private String negativeBtnText;
private OnClickListener positiveButtonListener = null;
private OnClickListener negativeButtonListener = null;
public static final String IAP_DIALOG_TAG = "IAP_dialog";
public interface OnClickListener {
void onClick();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
dialogWidth = getDialogWidth();
getDialog().getWindow().setLayout(dialogWidth, ActionBar.LayoutParams.WRAP_CONTENT);
}
@Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) {
getDialog().setDismissMessage(null);
}
super.onDestroyView();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view;
if (isDarkMode()) {
view = getActivity().getLayoutInflater().inflate(R.layout.dialog_dark, null);
} else {
view = getActivity().getLayoutInflater().inflate(R.layout.dialog_light, null);
}
((TextView) view.findViewById(R.id.dialog_title)).setText(title);
((TextView) view.findViewById(R.id.dialog_message)).setText(message);
((TextView) view.findViewById(R.id.dialog_message)).setLinksClickable(true);
((TextView) view.findViewById(R.id.dialog_message)).setMovementMethod(LinkMovementMethod.getInstance());
if (messageExtra == null || messageExtra.isEmpty()) {
view.findViewById(R.id.dialog_message_extra).setVisibility(View.GONE);
} else {
((TextView) view.findViewById(R.id.dialog_message_extra))
.setText(getString(R.string.ids_com_body_error_code_c) + " " + messageExtra);
view.findViewById(R.id.dialog_message_extra).setVisibility(View.VISIBLE);
}
((Button) view.findViewById(R.id.dialog_ok_btn)).setText(positiveBtnText);
view.findViewById(R.id.dialog_ok_btn).setOnClickListener(this);
if (negativeButtonListener == null) {
view.findViewById(R.id.dialog_cancel_btn).setVisibility(View.GONE);
view.findViewById(R.id.dialog_btn_padding).setVisibility(View.GONE);
} else {
((Button) view.findViewById(R.id.dialog_cancel_btn)).setText(negativeBtnText);
view.findViewById(R.id.dialog_cancel_btn).setVisibility(View.VISIBLE);
view.findViewById(R.id.dialog_cancel_btn).setOnClickListener(this);
view.findViewById(R.id.dialog_btn_padding).setVisibility(View.VISIBLE);
}
Dialog dialog = new Dialog(getActivity(), R.style.Theme_DialogTransparent);
dialog.setContentView(view);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
dialog.getWindow().setGravity(Gravity.BOTTOM);
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
TypedValue dimValue = new TypedValue();
if (isDarkMode()) {
getResources().getValue(R.integer.dim_dark, dimValue, true);
} else {
getResources().getValue(R.integer.dim_light, dimValue, true);
}
dialog.getWindow().setDimAmount(dimValue.getFloat());
return dialog;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
dialogWidth = getDialogWidth();
getDialog().getWindow().setLayout(dialogWidth, ActionBar.LayoutParams.WRAP_CONTENT);
}
public BaseDialogFragment setDialogTitle(String _title) {
if (!TextUtils.isEmpty(_title)) {
this.title = _title;
}
return this;
}
public BaseDialogFragment setDialogMessageText(CharSequence _message) {
if (!TextUtils.isEmpty(_message)) {
this.message = _message;
}
return this;
}
public BaseDialogFragment setDialogMessageExtra(String _extra) {
if (!TextUtils.isEmpty(_extra)) {
this.messageExtra = _extra;
}
return this;
}
public BaseDialogFragment setDialogPositiveButton(String _positiveBtnText, final OnClickListener listener) {
if (!TextUtils.isEmpty(_positiveBtnText)) {
positiveBtnText = _positiveBtnText;
} else {
positiveBtnText = (String) getText(android.R.string.ok);
}
if (listener != null) {
positiveButtonListener = listener;
}
return this;
}
public BaseDialogFragment setDialogNegativeButton(String _negativeBtnText, final OnClickListener listener) {
if (!TextUtils.isEmpty(_negativeBtnText)) {
negativeBtnText = _negativeBtnText;
}
if (listener != null) {
negativeButtonListener = listener;
}
return this;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.dialog_ok_btn) {
Runnable OkBtnRunnable = new Runnable() {
@Override
public void run() {
positiveButtonListener.onClick();
}
};
OkBtnRunnable.run();
} else if (v.getId() == R.id.dialog_cancel_btn) {
Runnable CancelBtnRunnable = new Runnable() {
@Override
public void run() {
negativeButtonListener.onClick();
}
};
CancelBtnRunnable.run();
}
dismiss();
}
private int getDialogWidth() {
TypedValue outValue = new TypedValue();
getResources().getValue(R.integer.dialog_width_percentage, outValue, true);
float ratio = outValue.getFloat();
int width = (int) (getResources().getDisplayMetrics().widthPixels * ratio);
return width;
}
private boolean isDarkMode() {
try {
// getContext() requires M OS or higher version
int nightModeFlags = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
nightModeFlags = getContext().getResources().getConfiguration().uiMode &
Configuration.UI_MODE_NIGHT_MASK;
}
switch (nightModeFlags) {
case Configuration.UI_MODE_NIGHT_YES:
return true;
case Configuration.UI_MODE_NIGHT_NO:
case Configuration.UI_MODE_NIGHT_UNDEFINED:
default:
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}

View File

@@ -0,0 +1,192 @@
package com.samsung.android.sdk.iap.lib.helper;
/**
* Created by sangbum7.kim on 2017-07-17.
*/
public class HelperDefine {
public static final String HELPER_VERSION = "6.1.0.004";
// IAP Signature HashCode - Used to validate IAP package
// ========================================================================
public static final int APPS_SIGNATURE_HASHCODE = 0x79998D13;
public static final int APPS_PACKAGE_VERSION = 450301000;
public static final int APPS_PACKAGE_VERSION_GO = 510130000;
public static final int APPS_PACKAGE_VERSION_INDIA = 660107000;
// ========================================================================
// Name of IAP Package and Service
// ========================================================================
public static final String GALAXY_PACKAGE_NAME = "com.sec.android.app.samsungapps";
public static final String IAP_PACKAGE_NAME = "com.samsung.android.iap";
public static final String IAP_SERVICE_NAME =
"com.samsung.android.iap.service.IAPService";
// ========================================================================
// result code for binding to IAPService
// ========================================================================
public static final int IAP_RESPONSE_RESULT_OK = 0;
public static final int IAP_RESPONSE_RESULT_UNAVAILABLE = 2;
// ========================================================================
// BUNDLE KEY
// ========================================================================
public static final String KEY_NAME_THIRD_PARTY_NAME = "THIRD_PARTY_NAME";
public static final String KEY_NAME_STATUS_CODE = "STATUS_CODE";
public static final String KEY_NAME_ERROR_STRING = "ERROR_STRING";
public static final String KEY_NAME_ERROR_DETAILS = "ERROR_DETAILS";
public static final String KEY_NAME_ITEM_ID = "ITEM_ID";
public static final String KEY_NAME_PASSTHROUGH_ID = "PASSTHROUGH_ID";
public static final String KEY_NAME_RESULT_LIST = "RESULT_LIST";
public static final String KEY_NAME_OPERATION_MODE = "OPERATION_MODE";
public static final String KEY_NAME_RESULT_OBJECT = "RESULT_OBJECT";
public static final String KEY_NAME_VERSION_CODE = "VERSION_CODE";
public static final String NEXT_PAGING_INDEX = "NEXT_PAGING_INDEX";
// ========================================================================
// Item Type
// ========================================================================
public static final String PRODUCT_TYPE_ITEM = "item";
public static final String PRODUCT_TYPE_SUBSCRIPTION = "subscription";
public static final String PRODUCT_TYPE_ALL = "all";
// Define request code to IAPService.
// ========================================================================
public static final int REQUEST_CODE_IS_IAP_PAYMENT = 1;
public static final int REQUEST_CODE_IS_ACCOUNT_CERTIFICATION = 2;
public static final int REQUEST_CODE_IS_ENABLE_APPS = 3;
// ========================================================================
// Define dialog type to DialogActivity
public static final int DIALOG_TYPE_NONE = 0;
public static final int DIALOG_TYPE_NOTIFICATION = 1;
public static final int DIALOG_TYPE_INVALID_PACKAGE = 2;
public static final int DIALOG_TYPE_DISABLE_APPLICATION = 3;
public static final int DIALOG_TYPE_APPS_DETAIL = 4;
// Define request parameter to IAPService
// ========================================================================
public static final int PASSTHROGUH_MAX_LENGTH = 255;
// ========================================================================
// Define status code notify to 3rd-party application
// ========================================================================
/**
* Success
*/
final public static int IAP_ERROR_NONE = 0;
/**
* Payment is cancelled
*/
final public static int IAP_PAYMENT_IS_CANCELED = 1;
/**
* IAP initialization error
*/
final public static int IAP_ERROR_INITIALIZATION = -1000;
/**
* IAP need to be upgraded
*/
final public static int IAP_ERROR_NEED_APP_UPGRADE = -1001;
/**
* Common error
*/
final public static int IAP_ERROR_COMMON = -1002;
/**
* Repurchase NON-CONSUMABLE item
*/
final public static int IAP_ERROR_ALREADY_PURCHASED = -1003;
/**
* When PaymentMethodList Activity is called without Bundle data
*/
final public static int IAP_ERROR_WHILE_RUNNING = -1004;
/**
* does not exist item or item group id
*/
final public static int IAP_ERROR_PRODUCT_DOES_NOT_EXIST = -1005;
/**
* After purchase request not received the results can not be determined whether to buy. So, the confirmation of purchase list is needed.
*/
final public static int IAP_ERROR_CONFIRM_INBOX = -1006;
/**
* Error when item group id does not exist
*/
public static final int IAP_ERROR_ITEM_GROUP_DOES_NOT_EXIST = -1007;
/**
* Error when network is not available
*/
public static final int IAP_ERROR_NETWORK_NOT_AVAILABLE = -1008;
/**
* IOException
*/
public static final int IAP_ERROR_IOEXCEPTION_ERROR = -1009;
/**
* SocketTimeoutException
*/
public static final int IAP_ERROR_SOCKET_TIMEOUT = -1010;
/**
* ConnectTimeoutException
*/
public static final int IAP_ERROR_CONNECT_TIMEOUT = -1011;
/**
* The Item is not for sale in the country
*/
public static final int IAP_ERROR_NOT_EXIST_LOCAL_PRICE = -1012;
/**
* IAP is not serviced in the country
*/
public static final int IAP_ERROR_NOT_AVAILABLE_SHOP = -1013;
/**
* SA not logged in
*/
public static final int IAP_ERROR_NEED_SA_LOGIN = -1014;
// ========================================================================
/**
* initial state
*/
protected static final int STATE_TERM = 0;
/**
* state of bound to IAPService successfully
*/
protected static final int STATE_BINDING = 1;
/**
* state of InitIapTask successfully finished
*/
protected static final int STATE_READY = 2;
// ========================================================================
// IAP Operation Mode
// ========================================================================
public enum OperationMode {
OPERATION_MODE_TEST_FAILURE(-1),
OPERATION_MODE_PRODUCTION(0),
OPERATION_MODE_TEST(1);
final private int value;
OperationMode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
// ========================================================================
}

View File

@@ -0,0 +1,102 @@
package com.samsung.android.sdk.iap.lib.helper;
import com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask;
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
import com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask;
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
import com.samsung.android.sdk.iap.lib.listener.OnGetProductsDetailsListener;
import com.samsung.android.sdk.iap.lib.listener.OnPaymentListener;
/**
* Created by sangbum7.kim on 2017-08-29.
*/
public class HelperListenerManager {
private static HelperListenerManager mInstance = null;
private OnGetProductsDetailsListener mOnGetProductsDetailsListener = null;
private OnGetOwnedListListener mOnGetOwnedListListener = null;
private OnConsumePurchasedItemsListener mOnConsumePurchasedItemsListener = null;
private OnPaymentListener mOnPaymentListener = null;
/**
* HelperListenerManager singleton reference method
*/
public static HelperListenerManager getInstance() {
if (mInstance == null) {
mInstance = new HelperListenerManager();
}
return mInstance;
}
public static void destroy() {
mInstance = null;
}
/**
* HelperListenerManager constructor
*/
private HelperListenerManager() {
mOnGetProductsDetailsListener = null;
mOnGetOwnedListListener = null;
mOnConsumePurchasedItemsListener = null;
mOnPaymentListener = null;
}
/**
* Register {@link OnGetProductsDetailsListener} callback interface to be invoked when {@link GetProductsDetailsTask} has been finished.
*
* @param _onGetProductsDetailsListener
*/
public void setOnGetProductsDetailsListener(OnGetProductsDetailsListener _onGetProductsDetailsListener) {
mOnGetProductsDetailsListener = _onGetProductsDetailsListener;
}
public OnGetProductsDetailsListener getOnGetProductsDetailsListener() {
return mOnGetProductsDetailsListener;
}
/**
* Register {@link OnGetOwnedListListener} callback interface to be invoked when {@link GetOwnedListTask} has been finished.
*
* @param _onGetOwnedListListener
*/
public void setOnGetOwnedListListener(OnGetOwnedListListener _onGetOwnedListListener) {
mOnGetOwnedListListener = _onGetOwnedListListener;
}
public OnGetOwnedListListener getOnGetOwnedListListener() {
return mOnGetOwnedListListener;
}
/**
* Register {@link OnConsumePurchasedItemsListener} callback interface to be invoked when {@link ConsumePurchasedItemsTask} has been finished.
*
* @param _onConsumePurchasedItemsListener
*/
public void setOnConsumePurchasedItemsListener(OnConsumePurchasedItemsListener _onConsumePurchasedItemsListener) {
mOnConsumePurchasedItemsListener = _onConsumePurchasedItemsListener;
}
public OnConsumePurchasedItemsListener getOnConsumePurchasedItemsListener() {
return mOnConsumePurchasedItemsListener;
}
/**
* Register a callback interface to be invoked when Purchase Process has been finished.
*
* @param _onPaymentListener
*/
public void setOnPaymentListener(OnPaymentListener _onPaymentListener) {
mOnPaymentListener = _onPaymentListener;
}
public OnPaymentListener getOnPaymentListener() {
return mOnPaymentListener;
}
}

View File

@@ -0,0 +1,335 @@
package com.samsung.android.sdk.iap.lib.helper;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.text.Html;
import android.util.Log;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.activity.BaseActivity;
import com.samsung.android.sdk.iap.lib.dialog.BaseDialogFragment;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
/**
* Created by sangbum7.kim on 2017-08-17.
*/
public class HelperUtil {
private static final String TAG = HelperUtil.class.getSimpleName();
/**
* show a dialog
*
* @param activity The activity adding the fragment that displays a dialog
* @param title The title to display
* @param message The message to display
* @param positiveListener The listener to be invoked when the positive button of the dialog is pressed
* @param negativeListener The listener to be invoked when the negative button of the dialog is pressed
*/
public static void showIapErrorDialog(
final Activity activity,
String title,
String message,
final BaseDialogFragment.OnClickListener positiveListener,
final BaseDialogFragment.OnClickListener negativeListener) {
showIapErrorDialog(activity, title, message, "", positiveListener, negativeListener);
}
/**
* show a dialog
*
* @param activity The activity adding the fragment that displays a dialog
* @param title The title to display
* @param message The message to display
* @param messageExtra The extra message to display
* @param positiveListener The listener to be invoked when the positive button of the dialog is pressed
* @param negativeListener The listener to be invoked when the negative button of the dialog is pressed
*/
public static void showIapErrorDialog(
final Activity activity,
String title,
String message,
String messageExtra,
final BaseDialogFragment.OnClickListener positiveListener,
final BaseDialogFragment.OnClickListener negativeListener) {
new BaseDialogFragment()
.setDialogTitle(title)
.setDialogMessageText(message)
.setDialogMessageExtra(messageExtra)
.setDialogPositiveButton(activity.getString(android.R.string.ok), positiveListener)
.setDialogNegativeButton(activity.getString(android.R.string.cancel), negativeListener)
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
}
/**
* show a dialog to update the Galaxy Store
*
* @param activity The activity adding the fragment that displays a dialog
*/
public static void showUpdateGalaxyStoreDialog(final Activity activity) {
// TODO: both title and message will be changed as UX Guide
new BaseDialogFragment()
.setDialogTitle(activity.getString(R.string.dream_ph_pheader_couldnt_complete_purchase))
.setDialogMessageText(activity.getString(
R.string.dream_ph_body_to_complete_this_purchase_you_need_to_update_the_galaxy_store))
.setDialogPositiveButton(
activity.getString(android.R.string.ok),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
goGalaxyStoreDetailPage(activity.getApplicationContext());
activity.finish();
}
})
.setDialogNegativeButton(
activity.getString(android.R.string.cancel),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
activity.finish();
}
})
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
}
/**
* show a dialog to enable the Galaxy Store
*
* @param activity The activity adding the fragment that displays a dialog
*/
public static void showEnableGalaxyStoreDialog(final Activity activity) {
// TODO: both title and message will be changed as UX Guide
new BaseDialogFragment()
.setDialogTitle(activity.getString(R.string.dream_ph_pheader_couldnt_complete_purchase))
.setDialogMessageText(
activity.getString(R.string.dream_ph_body_to_complete_this_purchase_you_need_to_enable_the_galaxy_store_in_settings))
.setDialogPositiveButton(
activity.getString(android.R.string.ok),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + HelperDefine.GALAXY_PACKAGE_NAME));
activity.startActivityForResult(intent, HelperDefine.REQUEST_CODE_IS_ENABLE_APPS);
activity.finish();
}
})
.setDialogNegativeButton(
activity.getString(android.R.string.cancel),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
activity.finish();
}
})
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
}
/**
* show a dialog to notice that the Galaxy Store is invalid
*
* @param activity The activity adding the fragment that displays a dialog
*/
public static void showInvalidGalaxyStoreDialog(final Activity activity) {
final String ERROR_ISSUER_IAP_CLIENT = "IC";
final int ERROR_CODE_INVALID_GALAXY_STORE = 10002;
String source = String.format(
activity.getString(
R.string.dream_ph_body_contact_p1sscustomer_servicep2ss_for_more_information_n_nerror_code_c_p3ss),
"<a href=\"http://help.content.samsung.com\">", "</a>",
ERROR_ISSUER_IAP_CLIENT + ERROR_CODE_INVALID_GALAXY_STORE);
CharSequence errorMessage;
// fromHtml(String) was deprecated in N OS
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
errorMessage = Html.fromHtml(source);
} else {
errorMessage = Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
}
new BaseDialogFragment()
.setDialogTitle(activity.getString(R.string.dream_ph_pheader_couldnt_complete_purchase))
.setDialogMessageText(errorMessage)
.setDialogMessageExtra(ERROR_ISSUER_IAP_CLIENT + ERROR_CODE_INVALID_GALAXY_STORE)
.setDialogPositiveButton(
activity.getString(android.R.string.ok),
new BaseDialogFragment.OnClickListener() {
@Override
public void onClick() {
activity.finish();
}
})
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
}
private static void goGalaxyStoreDetailPage(Context context) {
// Link of Galaxy Store for IAP install
// ------------------------------------------------------------
Uri appsDeepLink = Uri.parse("samsungapps://StoreVersionInfo/");
// ------------------------------------------------------------
Intent intent = new Intent();
intent.setData(appsDeepLink);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(intent);
}
}
/**
* Check that Galaxy Store is installed
*
* @param _context Context
* @return If it is true Galaxy Store is installed. otherwise, not installed.
*/
static public boolean isInstalledAppsPackage(Context _context) {
PackageManager pm = _context.getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo(HelperDefine.GALAXY_PACKAGE_NAME, PackageManager.GET_META_DATA);
int versionType = packageInfo.versionCode / 100000000;
Log.i(TAG, "isInstalledAppsPackage : " + packageInfo.versionCode + ", " + versionType);
switch (versionType) {
case 4: {
return packageInfo.versionCode >= HelperDefine.APPS_PACKAGE_VERSION;
}
case 5: {
return true;
// return packageInfo.versionCode >= HelperDefine.APPS_PACKAGE_VERSION_GO;
}
case 6: {
return packageInfo.versionCode >= HelperDefine.APPS_PACKAGE_VERSION_INDIA;
}
// Unverified version
default:
return true;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
}
static public boolean isEnabledAppsPackage(Context context) {
//// TODO: 2017-08-16 Make sure the status is normal
int status = context.getPackageManager().getApplicationEnabledSetting(HelperDefine.GALAXY_PACKAGE_NAME);
Log.i(TAG, "isEnabledAppsPackage: status " + status);
return !((status == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) || (status == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
}
/**
* check validation of installed Galaxy Store in your device
*
* @param _context
* @return If it is true Galaxy Store is valid. otherwise, is not valid.
*/
static public boolean isValidAppsPackage(Context _context) {
boolean result = true;
try {
Signature[] sigs = _context.getPackageManager().getPackageInfo(
HelperDefine.GALAXY_PACKAGE_NAME,
PackageManager.GET_SIGNATURES).signatures;
if (sigs[0].hashCode() != HelperDefine.APPS_SIGNATURE_HASHCODE) {
result = false;
}
} catch (Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
/**
* SamsungAccount authentication
*
* @param _activity
*/
static public boolean startAccountActivity(final Activity _activity) {
ComponentName com = new ComponentName(HelperDefine.GALAXY_PACKAGE_NAME,
HelperDefine.IAP_PACKAGE_NAME + ".activity.AccountActivity");
Context context = _activity.getApplicationContext();
Intent intent = new Intent();
intent.setComponent(com);
if (intent.resolveActivity(context.getPackageManager()) != null) {
_activity.startActivityForResult(intent,
HelperDefine.REQUEST_CODE_IS_ACCOUNT_CERTIFICATION);
return true;
}
return false;
}
/**
* go to about page of Galaxy Store in order to install IAP package.
*/
static public void installAppsPackage(final BaseActivity _activity) {
// Set error in order to notify result to third-party application.
// ====================================================================
ErrorVo errorVo = new ErrorVo();
_activity.setErrorVo(errorVo);
errorVo.setError(
HelperDefine.IAP_PAYMENT_IS_CANCELED,
_activity.getString(R.string.mids_sapps_pop_payment_canceled));
// ====================================================================
// Show information dialog
// ====================================================================
showUpdateGalaxyStoreDialog(_activity);
// ====================================================================
}
static public int checkAppsPackage(Context _context) {
// 1. If Galaxy Store is installed
// ====================================================================
if (HelperUtil.isInstalledAppsPackage(_context)) {
// 1) If Galaxy Store is enabled
// ================================================================
if (!HelperUtil.isEnabledAppsPackage(_context)) {
return HelperDefine.DIALOG_TYPE_DISABLE_APPLICATION;
// ================================================================
// 2) If Galaxy Store is valid
// ================================================================
} else if (HelperUtil.isValidAppsPackage(_context)) {
return HelperDefine.DIALOG_TYPE_NONE;
} else {
// ------------------------------------------------------------
// show alert dialog if Galaxy Store is invalid
// ------------------------------------------------------------
return HelperDefine.DIALOG_TYPE_INVALID_PACKAGE;
// ------------------------------------------------------------
}
// ================================================================
// ====================================================================
// 2. If Galaxy Store is not installed
// ====================================================================
} else {
// When user click the OK button on the dialog,
// go to Galaxy Store Detail page
// ====================================================================
return HelperDefine.DIALOG_TYPE_APPS_DETAIL;
}
// ====================================================================
}
}

View File

@@ -0,0 +1,642 @@
package com.samsung.android.sdk.iap.lib.helper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask.Status;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.samsung.android.iap.IAPConnector;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.activity.CheckPackageActivity;
import com.samsung.android.sdk.iap.lib.activity.PaymentActivity;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask;
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
import com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask;
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
import com.samsung.android.sdk.iap.lib.listener.OnGetProductsDetailsListener;
import com.samsung.android.sdk.iap.lib.listener.OnPaymentListener;
import com.samsung.android.sdk.iap.lib.service.BaseService;
import com.samsung.android.sdk.iap.lib.service.ConsumePurchasedItems;
import com.samsung.android.sdk.iap.lib.service.OwnedProduct;
import com.samsung.android.sdk.iap.lib.service.ProductsDetails;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import java.util.ArrayList;
public class IapHelper extends HelperDefine {
private static final String TAG = IapHelper.class.getSimpleName();
/**
* Please double-check this mode before release.
*/
private int mMode = HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION.getValue();
// ========================================================================
private Context mContext = null;
private IAPConnector mIapConnector = null;
private ServiceConnection mServiceConn = null;
// AsyncTask for API
// ========================================================================
private com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask mGetProductsDetailsTask = null;
private com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask mGetOwnedListTask = null;
private com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask mConsumePurchasedItemsTask = null;
// ========================================================================
private ArrayList<BaseService> mServiceQueue = new ArrayList<BaseService>();
private BaseService mCurrentService = null;
// API listener
private HelperListenerManager mListenerInstance = null;
private static IapHelper mInstance = null;
// State of IAP Service
// ========================================================================
private int mState = HelperDefine.STATE_TERM;
private final static Object mOperationLock = new Object();
static boolean mOperationRunningFlag = false;
private boolean mShowErrorDialog = true;
// ########################################################################
// ########################################################################
// 1. SamsungIAPHeler object create and reference
// ########################################################################
// ########################################################################
/**
* IapHelper constructor
*
* @param _context
*/
private IapHelper(Context _context) {
_setContextAndMode(_context);
_setListenerInstance();
}
/**
* IapHelper singleton reference method
*
* @param _context Context
*/
public static IapHelper getInstance(Context _context) {
Log.i(TAG, "IAP Helper version : " + HelperDefine.HELPER_VERSION);
if (null == mInstance) {
mInstance = new IapHelper(_context);
} else {
mInstance._setContextAndMode(_context);
}
return mInstance;
}
public void setOperationMode(OperationMode _mode) {
mMode = _mode.getValue();
}
private void _setContextAndMode(Context _context) {
mContext = _context.getApplicationContext();
}
private void _setListenerInstance() {
if (mListenerInstance != null) {
mListenerInstance.destroy();
mListenerInstance = null;
}
mListenerInstance = HelperListenerManager.getInstance();
}
// ########################################################################
// ########################################################################
// 2. Binding for IAPService
// ########################################################################
// ########################################################################
/**
* bind to IAPService
*/
public void bindIapService() {
Log.i(TAG, "bindIapService()");
// exit If already bound
// ====================================================================
if (mState >= HelperDefine.STATE_BINDING) {
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_OK);
return;
}
// ====================================================================
// Connection to IAP service
// ====================================================================
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName _name) {
Log.i(TAG, "IAP Service Disconnected...");
mState = HelperDefine.STATE_TERM;
mIapConnector = null;
mServiceConn = null;
}
@Override
public void onServiceConnected
(
ComponentName _name,
IBinder _service
) {
Log.i(TAG, "IAP Service Connected...");
mIapConnector = IAPConnector.Stub.asInterface(_service);
if (mIapConnector != null) {
mState = HelperDefine.STATE_BINDING;
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_OK);
} else {
mState = HelperDefine.STATE_TERM;
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_UNAVAILABLE);
}
}
};
// ====================================================================
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName(HelperDefine.GALAXY_PACKAGE_NAME, HelperDefine.IAP_SERVICE_NAME));
// bind to IAPService
// ====================================================================
try {
if (mContext == null || mContext.bindService(serviceIntent,
mServiceConn,
Context.BIND_AUTO_CREATE) == false) {
mState = HelperDefine.STATE_TERM;
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_UNAVAILABLE);
}
} catch (SecurityException e) {
Log.e(TAG, "SecurityException : " + e);
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_UNAVAILABLE);
}
// ====================================================================
}
protected void onBindIapFinished(int _result) {
Log.i(TAG, "onBindIapFinished");
if (_result == HelperDefine.IAP_RESPONSE_RESULT_OK) {
if (getServiceProcess() != null) {
getServiceProcess().runServiceProcess();
}
}
// ============================================================
// 2) If IAPService is not bound.
// ============================================================
else {
if (getServiceProcess() != null) {
ErrorVo errorVo = new ErrorVo();
errorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION,
mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred) + "[Lib_Bind]");
errorVo.setShowDialog(mShowErrorDialog);
getServiceProcess().setErrorVo(errorVo);
getServiceProcess().onEndProcess();
}
}
}
/* ########################################################################
* ########################################################################
* 3. IAP APIs.
* ########################################################################
* ##################################################################### */
///////////////////////////////////////////////////////////////////////////
// 3.1) getProductsDetails ///////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* <PRE>
* This load item list by starting productActivity in this library, and the result will be sent to {@link OnGetProductsDetailsListener} Callback
* interface.
*
* </PRE>
*
* @param _productIds
* @param _onGetProductsDetailsListener
*/
public void getProductsDetails
(
String _productIds,
OnGetProductsDetailsListener _onGetProductsDetailsListener
) {
try {
if (_onGetProductsDetailsListener == null) {
throw new Exception("_onGetProductsDetailsListener is null");
}
ProductsDetails productsDetails = new ProductsDetails(mInstance, mContext, _onGetProductsDetailsListener);
productsDetails.setProductId(_productIds);
setServiceProcess(productsDetails);
IapStartInProgressFlag();
checkAppsPackage();
} catch (IapInProgressException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* execute GetProductsDetailsTask
*/
public boolean safeGetProductsDetails
(
ProductsDetails _baseService,
String _productIDs,
boolean _showErrorDialog
) {
try {
if (mGetProductsDetailsTask != null &&
mGetProductsDetailsTask.getStatus() != Status.FINISHED) {
mGetProductsDetailsTask.cancel(true);
}
if (mIapConnector == null || mContext == null) {
return false;
} else {
mGetProductsDetailsTask = new com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask(_baseService,
mIapConnector,
mContext,
_productIDs,
_showErrorDialog,
mMode);
mGetProductsDetailsTask.execute();
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
///////////////////////////////////////////////////////////////////////////
// 3.2) getOwnedList //////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* <PRE>
* This load owned product list by starting OwnedListActivity in this library, and the result will be sent to {@link OnGetOwnedListListener}
* Callback interface.
*
* </PRE>
*
* @param _productType
* @param _onGetOwnedListListener
*/
public boolean getOwnedList
(
String _productType,
OnGetOwnedListListener _onGetOwnedListListener
) {
Log.i(TAG, "getOwnedList");
try {
if (_onGetOwnedListListener == null) {
throw new Exception("_onGetOwnedListListener is null");
}
if (TextUtils.isEmpty(_productType)) {
throw new Exception("_productType is null or empty");
}
OwnedProduct ownedProduct = new OwnedProduct(mInstance, mContext, _onGetOwnedListListener);
ownedProduct.setProductType(_productType);
setServiceProcess(ownedProduct);
IapStartInProgressFlag();
checkAppsPackage();
} catch (IapInProgressException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* execute GetOwnedListTask
*/
public boolean safeGetOwnedList
(
OwnedProduct _baseService,
String _productType,
boolean _showErrorDialog
) {
try {
if (mGetOwnedListTask != null &&
mGetOwnedListTask.getStatus() != Status.FINISHED) {
mGetOwnedListTask.cancel(true);
}
if (mIapConnector == null || mContext == null) {
return false;
} else {
mGetOwnedListTask = new com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask(_baseService,
mIapConnector,
mContext,
_productType,
_showErrorDialog,
mMode);
mGetOwnedListTask.execute();
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
///////////////////////////////////////////////////////////////////////////
// 3.3) consumePurchasedItems /////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* <PRE>
* This load item list by starting OwnedListActivity in this library, and the result will be sent to {@link OnConsumePurchasedItemsListener}
* Callback interface.
*
* </PRE>
*
* @param _purchaseIds
* @param _onConsumePurchasedItemsListener
*/
public boolean consumePurchasedItems
(
String _purchaseIds,
OnConsumePurchasedItemsListener _onConsumePurchasedItemsListener
) {
try {
if (_onConsumePurchasedItemsListener == null) {
throw new Exception("_onConsumePurchasedItemsListener is null");
}
if (TextUtils.isEmpty(_purchaseIds)) {
throw new Exception("_purchaseIds is null or empty");
}
ConsumePurchasedItems consumePurchasedItems = new ConsumePurchasedItems(mInstance, mContext, _onConsumePurchasedItemsListener);
consumePurchasedItems.setPurchaseIds(_purchaseIds);
setServiceProcess(consumePurchasedItems);
IapStartInProgressFlag();
checkAppsPackage();
} catch (IapInProgressException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* execute ConsumePurchasedItemsTask
*/
public boolean safeConsumePurchasedItems
(
ConsumePurchasedItems _baseService,
String _purchaseIds,
boolean _showErrorDialog
) {
try {
if (mConsumePurchasedItemsTask != null &&
mConsumePurchasedItemsTask.getStatus() != Status.FINISHED) {
mConsumePurchasedItemsTask.cancel(true);
}
mConsumePurchasedItemsTask = new com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask(_baseService,
mIapConnector,
mContext,
_purchaseIds,
_showErrorDialog,
mMode);
mConsumePurchasedItemsTask.execute();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
///////////////////////////////////////////////////////////////////////////
// 3.4) startPurchase / ///////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* <PRE>
* Start payment process by starting {@link PaymentActivity} in this library, and result will be sent to {@link OnPaymentListener} interface. To
* do that, PaymentActivity must be described in AndroidManifest.xml of third-party application as below.
* <p>
* &lt;activity android:name="com.sec.android.iap.lib.activity.PaymentActivity" android:theme="@style/Theme.Empty"
* android:configChanges="orientation|screenSize"/&gt;
* </PRE>
*
* @param _itemId
* @param _passThroughParam
* @param _onPaymentListener
*/
public boolean startPayment
(
String _itemId,
String _passThroughParam,
OnPaymentListener _onPaymentListener
) {
try {
if (_onPaymentListener == null) {
throw new Exception("OnPaymentListener is null");
}
if (TextUtils.isEmpty(_itemId)) {
throw new Exception("_itemId is null or empty");
}
if (_passThroughParam != null && _passThroughParam.getBytes("UTF-8").length > HelperDefine.PASSTHROGUH_MAX_LENGTH) {
throw new Exception("PassThroughParam length exceeded (MAX " + HelperDefine.PASSTHROGUH_MAX_LENGTH + ")");
}
IapStartInProgressFlag();
mListenerInstance.setOnPaymentListener(_onPaymentListener);
Intent intent = new Intent(mContext, PaymentActivity.class);
intent.putExtra("ItemId", _itemId);
String encodedPassThroughParam = "";
if (_passThroughParam != null) {
encodedPassThroughParam = Base64.encodeToString(_passThroughParam.getBytes("UTF-8"), 0);
}
intent.putExtra("PassThroughParam", encodedPassThroughParam);
intent.putExtra("ShowErrorDialog", mShowErrorDialog);
intent.putExtra("OperationMode", mMode);
Log.i(TAG, "startPayment: " + mMode);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} catch (IapInProgressException e) {
e.printStackTrace();
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* <PRE>
* Start payment process by starting {@link PaymentActivity} in this library, and result will be sent to {@link OnPaymentListener} interface. To
* do that, PaymentActivity must be described in AndroidManifest.xml of third-party application as below.
* <p>
* &lt;activity android:name="com.sec.android.iap.lib.activity.PaymentActivity" android:theme="@style/Theme.Empty"
* android:configChanges="orientation|screenSize"/&gt;
* </PRE>
*
* @param _itemId
* @param _passThroughParam
* @param _showSuccessDialog Unused parameter.
* @param _onPaymentListener
* @deprecated
*/
public boolean startPayment
(
String _itemId,
String _passThroughParam,
boolean _showSuccessDialog,
OnPaymentListener _onPaymentListener
) {
return startPayment(_itemId, _passThroughParam, _onPaymentListener);
}
// ########################################################################
// ########################################################################
// 4. etc
// ########################################################################
// ########################################################################
/**
* Stop running task, {@link GetProductsDetailsTask}, {@link ConsumePurchasedItemsTask} or {@link GetOwnedListTask} } before dispose().
*/
private void stopTasksIfNotFinished() {
if (mGetProductsDetailsTask != null) {
if (mGetProductsDetailsTask.getStatus() != Status.FINISHED) {
Log.e(TAG, "stopTasksIfNotFinished: mGetProductsDetailsTask Status > " + mGetProductsDetailsTask.getStatus());
mGetProductsDetailsTask.cancel(true);
}
}
if (mGetOwnedListTask != null) {
if (mGetOwnedListTask.getStatus() != Status.FINISHED) {
Log.e(TAG, "stopTasksIfNotFinished: mGetOwnedListTask Status > " + mGetOwnedListTask.getStatus());
mGetOwnedListTask.cancel(true);
}
}
if (mConsumePurchasedItemsTask != null) {
if (mConsumePurchasedItemsTask.getStatus() != Status.FINISHED) {
Log.e(TAG, "stopTasksIfNotFinished: mConsumePurchasedItemsTask Status > " + mConsumePurchasedItemsTask.getStatus());
mConsumePurchasedItemsTask.cancel(true);
}
}
}
/**
* Unbind from IAPService and release used resources.
*/
public void dispose() {
stopTasksIfNotFinished();
if (mContext != null && mServiceConn != null) {
mContext.unbindService(mServiceConn);
}
mState = HelperDefine.STATE_TERM;
mServiceConn = null;
mIapConnector = null;
clearServiceProcess();
IapEndInProgressFlag();
}
void IapStartInProgressFlag() throws IapInProgressException {
Log.i(TAG, "IapStartInProgressFlag");
synchronized (mOperationLock) {
if (mOperationRunningFlag) {
throw new IapInProgressException("another operation is running");
}
mOperationRunningFlag = true;
}
}
void IapEndInProgressFlag() {
Log.i(TAG, "IapEndInProgressFlag");
synchronized (mOperationLock) {
mOperationRunningFlag = false;
}
}
protected static class IapInProgressException extends Exception {
public IapInProgressException(String message) {
super(message);
}
}
void checkAppsPackage() {
int checkResult = HelperUtil.checkAppsPackage(mContext);
if (checkResult == HelperDefine.DIALOG_TYPE_NONE) {
bindIapService();
} else {
Intent intent = new Intent(mContext, CheckPackageActivity.class);
intent.putExtra("DialogType", checkResult);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}
/**
* Sets whether error popup is displayed when payment is finished.
*/
public void setShowErrorDialog(boolean _showErrorDialog) {
this.mShowErrorDialog = _showErrorDialog;
}
public boolean getShowErrorDialog() {
return this.mShowErrorDialog;
}
public BaseService getServiceProcess() {
return getServiceProcess(false);
}
public BaseService getServiceProcess(boolean _nextProcess) {
if (mCurrentService == null || _nextProcess) {
mCurrentService = null;
if (mServiceQueue.size() > 0) {
mCurrentService = mServiceQueue.get(0);
mServiceQueue.remove(0);
}
}
return mCurrentService;
}
private void setServiceProcess(BaseService _baseService) {
mServiceQueue.add(_baseService);
}
private void clearServiceProcess() {
do {
if (mCurrentService != null) {
mCurrentService.releaseProcess();
}
mCurrentService = getServiceProcess(true);
} while (mCurrentService != null);
mServiceQueue.clear();
}
}

View File

@@ -0,0 +1,65 @@
package com.samsung.android.sdk.iap.lib.helper.task;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import com.samsung.android.iap.IAPConnector;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.service.BaseService;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
/**
* Created by sangbum7.kim on 2017-09-01.
*/
public class BaseTask extends AsyncTask<String, Object, Boolean> {
private static final String TAG = BaseTask.class.getSimpleName();
protected BaseService mBaseService;
protected IAPConnector mIapConnector;
protected Context mContext;
protected int mMode;
protected String mPackageName = "";
protected ErrorVo mErrorVo = new ErrorVo();
public BaseTask(BaseService _baseService,
IAPConnector _iapConnector,
Context _context,
boolean _showErrorDialog,
int _mode) {
mBaseService = _baseService;
mIapConnector = _iapConnector;
mContext = _context;
if (mContext != null) {
mPackageName = mContext.getPackageName();
}
mMode = _mode;
mErrorVo.setShowDialog(_showErrorDialog);
mBaseService.setErrorVo(mErrorVo);
}
@Override
protected Boolean doInBackground(String... params) {
return true;
}
@Override
protected void onPostExecute(Boolean _result) {
// ================================================================
if (!_result) {
mErrorVo.setError(mErrorVo.getErrorCode(), mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
}
// ================================================================
mBaseService.onEndProcess();
}
@Override
protected void onCancelled() {
Log.e(TAG, "onCancelled: task cancelled");
}
}

View File

@@ -0,0 +1,105 @@
package com.samsung.android.sdk.iap.lib.helper.task;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.samsung.android.iap.IAPConnector;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.service.ConsumePurchasedItems;
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
import java.util.ArrayList;
/**
* Asynchronized Task to load a list of items
*/
public class ConsumePurchasedItemsTask extends BaseTask {
private static final String TAG = GetOwnedListTask.class.getSimpleName();
private String mPurchaseIds = "";
ArrayList<ConsumeVo> mConsumeList = new ArrayList<ConsumeVo>();
public ConsumePurchasedItemsTask
(
ConsumePurchasedItems _baseService,
IAPConnector _iapConnector,
Context _context,
String _purchaseIds,
boolean _showErrorDialog,
int _mode
) {
super(_baseService, _iapConnector, _context, _showErrorDialog, _mode);
mPurchaseIds = _purchaseIds;
_baseService.setConsumeList(mConsumeList);
}
@Override
protected Boolean doInBackground(String... params) {
try {
// 1) call getItemList() method of IAPService
// ============================================================
Bundle bundle = mIapConnector.consumePurchasedItems(
mPackageName,
mPurchaseIds,
mMode);
// ============================================================
// 2) save status code, error string and extra String.
// ============================================================
if (bundle != null) {
mErrorVo.setError(bundle.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
bundle.getString(HelperDefine.KEY_NAME_ERROR_STRING));
} else {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
mContext.getString(
R.string.mids_sapps_pop_unknown_error_occurred));
}
// ============================================================
// 3) If item list is loaded successfully,
// make item list by Bundle data
// ============================================================
// ============================================================
// 3) If item list is loaded successfully,
// make item list by Bundle data
// ============================================================
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
if (bundle != null) {
ArrayList<String> consumePurchasedItemsStringList =
bundle.getStringArrayList(HelperDefine.KEY_NAME_RESULT_LIST);
if (consumePurchasedItemsStringList != null) {
for (String consumePurchasedItemString : consumePurchasedItemsStringList) {
ConsumeVo consumeVo = new ConsumeVo(consumePurchasedItemString);
mConsumeList.add(consumeVo);
}
} else {
Log.i(TAG, "Bundle Value 'RESULT_LIST' is null.");
}
}
}
// ============================================================
// 4) If failed, print log.
// ============================================================
else {
Log.e(TAG, mErrorVo.getErrorString());
}
// ============================================================
} catch (Exception e) {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
mContext.getString(
R.string.mids_sapps_pop_unknown_error_occurred));
e.printStackTrace();
return false;
}
return true;
}
}

View File

@@ -0,0 +1,112 @@
package com.samsung.android.sdk.iap.lib.helper.task;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.samsung.android.iap.IAPConnector;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.service.OwnedProduct;
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
import java.util.ArrayList;
/**
* Asynchronized Task to load a list of items
*/
public class GetOwnedListTask extends BaseTask {
private static final String TAG = GetOwnedListTask.class.getSimpleName();
private String mProductType = "";
ArrayList<OwnedProductVo> mOwnedList = new ArrayList<OwnedProductVo>();
public GetOwnedListTask
(
OwnedProduct _baseService,
IAPConnector _iapConnector,
Context _context,
String _productType,
boolean _showErrorDialog,
int _mode
) {
super(_baseService, _iapConnector, _context, _showErrorDialog, _mode);
mProductType = _productType;
_baseService.setOwnedList(mOwnedList);
}
@Override
protected Boolean doInBackground(String... params) {
Log.i(TAG, "doInBackground: start");
try {
int pagingIndex = 1;
do {
Log.i(TAG, "doInBackground: pagingIndex = " + pagingIndex);
// 1) call getItemList() method of IAPService
// ============================================================
Bundle bundle = mIapConnector.getOwnedList(
mPackageName,
mProductType,
pagingIndex,
mMode);
// ============================================================
// 2) save status code, error string and extra String.
// ============================================================
if (bundle != null) {
mErrorVo.setError(bundle.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
bundle.getString(HelperDefine.KEY_NAME_ERROR_STRING));
} else {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
mContext.getString(
R.string.mids_sapps_pop_unknown_error_occurred));
}
// ============================================================
// 3) If item list is loaded successfully,
// make item list by Bundle data
// ============================================================
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
if (bundle != null) {
String nextPagingIndex = bundle.getString(HelperDefine.NEXT_PAGING_INDEX);
if (nextPagingIndex != null && nextPagingIndex.length() > 0) {
pagingIndex = Integer.parseInt(nextPagingIndex);
} else {
pagingIndex = -1;
}
ArrayList<String> ownedProductStringList =
bundle.getStringArrayList(HelperDefine.KEY_NAME_RESULT_LIST);
if (ownedProductStringList != null) {
for (String ownedProductString : ownedProductStringList) {
OwnedProductVo ownedPrroductVo = new OwnedProductVo(ownedProductString);
mOwnedList.add(ownedPrroductVo);
}
} else {
Log.i(TAG, "Bundle Value 'RESULT_LIST' is null.");
}
}
}
// ============================================================
// 4) If failed, print log.
// ============================================================
else {
Log.e(TAG, mErrorVo.getErrorString());
return true;
}
// ============================================================
} while (pagingIndex > 0);
} catch (Exception e) {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
mContext.getString(
R.string.mids_sapps_pop_unknown_error_occurred));
e.printStackTrace();
return false;
}
return true;
}
}

View File

@@ -0,0 +1,115 @@
package com.samsung.android.sdk.iap.lib.helper.task;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.samsung.android.iap.IAPConnector;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.service.ProductsDetails;
import com.samsung.android.sdk.iap.lib.vo.ProductVo;
import java.util.ArrayList;
/**
* Asynchronized Task to load a list of items
*/
public class GetProductsDetailsTask extends BaseTask {
private static final String TAG = GetProductsDetailsTask.class.getSimpleName();
private String mProductIds = "";
ArrayList<ProductVo> mProductsDetails = new ArrayList<ProductVo>();
public GetProductsDetailsTask
(
ProductsDetails _baseService,
IAPConnector _iapConnector,
Context _context,
String _productIDs,
boolean _showErrorDialog,
int _mode
) {
super(_baseService, _iapConnector, _context, _showErrorDialog, _mode);
mProductIds = _productIDs;
_baseService.setProductsDetails(mProductsDetails);
}
@Override
protected Boolean doInBackground(String... params) {
try {
int pagingIndex = 1;
do {
// 1) call getProductsDetails() method of IAPService
// ---- Order Priority ----
// 1. if productIds is not empty, the infomations abouts products included in the productIds are returned
// 2. if productIds is empty, the infomations about all products in this package are returned on a page by page
// ============================================================
Bundle bundle = mIapConnector.getProductsDetails(
mPackageName,
mProductIds,
pagingIndex,
mMode);
// ============================================================
// 2) save status code, error string and extra String.
// ============================================================
if (bundle != null) {
mErrorVo.setError(bundle.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
bundle.getString(HelperDefine.KEY_NAME_ERROR_STRING));
} else {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
mContext.getString(
R.string.mids_sapps_pop_unknown_error_occurred));
}
// ============================================================
// 3) If item list is loaded successfully,
// make item list by Bundle data
// ============================================================
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
if (bundle != null) {
String nextPagingIndex = bundle.getString(HelperDefine.NEXT_PAGING_INDEX);
if (nextPagingIndex != null && nextPagingIndex.length() > 0) {
pagingIndex = Integer.parseInt(nextPagingIndex);
Log.i(TAG, "PagingIndex = " + nextPagingIndex);
} else {
pagingIndex = -1;
}
ArrayList<String> productStringList =
bundle.getStringArrayList(HelperDefine.KEY_NAME_RESULT_LIST);
if (productStringList != null) {
for (String productString : productStringList) {
ProductVo productVo = new ProductVo(productString);
mProductsDetails.add(productVo);
}
} else {
Log.i(TAG, "Bundle Value 'RESULT_LIST' is null.");
}
}
}
// ============================================================
// 4) If failed, print log.
// ============================================================
else {
Log.e(TAG, mErrorVo.getErrorString());
return true;
}
// ============================================================
} while (pagingIndex > 0);
} catch (Exception e) {
mErrorVo.setError(
HelperDefine.IAP_ERROR_COMMON,
mContext.getString(
R.string.mids_sapps_pop_unknown_error_occurred));
e.printStackTrace();
return false;
}
return true;
}
}

View File

@@ -0,0 +1,20 @@
package com.samsung.android.sdk.iap.lib.listener;
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import java.util.ArrayList;
/**
* Callback Interface used with {@link GetOwnedListTask}
*/
public interface OnConsumePurchasedItemsListener {
/**
* Callback method to be invoked when {@link GetOwnedListTask} has been finished.
*
* @param _errorVO
* @param _consumeList
*/
void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList);
}

View File

@@ -0,0 +1,20 @@
package com.samsung.android.sdk.iap.lib.listener;
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
import java.util.ArrayList;
/**
* Callback Interface used with {@link GetOwnedListTask}
*/
public interface OnGetOwnedListListener {
/**
* Callback method to be invoked when {@link GetOwnedListTask} has been finished.
*
* @param _errorVO
* @param _ownedList
*/
void onGetOwnedProducts(ErrorVo _errorVO, ArrayList<OwnedProductVo> _ownedList);
}

View File

@@ -0,0 +1,20 @@
package com.samsung.android.sdk.iap.lib.listener;
import com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import com.samsung.android.sdk.iap.lib.vo.ProductVo;
import java.util.ArrayList;
/**
* Callback Interface used with {@link GetProductsDetailsTask}
*/
public interface OnGetProductsDetailsListener {
/**
* Callback method to be invoked when {@link GetProductsDetailsTask} has been finished.
*
* @param _errorVO
* @param _productList
*/
void onGetProducts(ErrorVo _errorVO, ArrayList<ProductVo> _productList);
}

View File

@@ -0,0 +1,13 @@
package com.samsung.android.sdk.iap.lib.listener;
/**
* Callback Interface to be invoked when bind to IAPService has been finished.
*/
public interface OnIapBindListener {
/**
* Callback method to be invoked after binding to IAP service successfully.
*
* @param result
*/
public void onBindIapFinished(int result);
}

View File

@@ -0,0 +1,15 @@
package com.samsung.android.sdk.iap.lib.listener;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import com.samsung.android.sdk.iap.lib.vo.PurchaseVo;
/**
* Callback Interface to be invoked when payment has been finished.
*/
public interface OnPaymentListener {
/**
* Callback method to be invoked when payment has been finished. There is return data for result of financial transaction whenever it was
* successful or failed.
*/
void onPayment(ErrorVo _errorVO, PurchaseVo _purchaseVO);
}

View File

@@ -0,0 +1,8 @@
package com.samsung.android.sdk.iap.lib.listener;
/**
* Created by sangbum7.kim on 2018-02-28.
*/
public interface OnSucceedBind {
}

View File

@@ -0,0 +1,76 @@
package com.samsung.android.sdk.iap.lib.service;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.activity.AccountActivity;
import com.samsung.android.sdk.iap.lib.activity.DialogActivity;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
/**
* Created by sangbum7.kim on 2018-02-28.
*/
public abstract class BaseService {
private static final String TAG = BaseService.class.getSimpleName();
protected ErrorVo mErrorVo = new ErrorVo();
protected IapHelper mIapHelper = null;
protected Context mContext = null;
public BaseService(IapHelper _iapHelper, Context _context) {
mIapHelper = _iapHelper;
mContext = _context;
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
}
public ErrorVo getErrorVo() {
return mErrorVo;
}
public void setErrorVo(ErrorVo mErrorVo) {
this.mErrorVo = mErrorVo;
}
public abstract void runServiceProcess();
public void onEndProcess() {
Log.i(TAG, "BaseService.onEndProcess");
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NEED_SA_LOGIN) {
Intent intent = new Intent(mContext, AccountActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
return;
} else if (mErrorVo.getErrorCode() != HelperDefine.IAP_ERROR_NONE) {
if (mErrorVo.getErrorCode() != HelperDefine.IAP_ERROR_NETWORK_NOT_AVAILABLE && mErrorVo.isShowDialog()) {
Intent intent = new Intent(mContext, DialogActivity.class);
intent.putExtra("Title", mContext.getString(R.string.dream_ph_pheader_couldnt_complete_purchase));
intent.putExtra("Message", mErrorVo.getErrorString());
intent.putExtra("DialogType", HelperDefine.DIALOG_TYPE_NOTIFICATION);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}
if (mIapHelper != null) {
BaseService baseService = mIapHelper.getServiceProcess(true);
if (baseService != null) {
baseService.runServiceProcess();
} else {
mIapHelper.dispose();
}
}
onReleaseProcess();
}
public void releaseProcess() {
onReleaseProcess();
}
abstract void onReleaseProcess();
}

View File

@@ -0,0 +1,62 @@
package com.samsung.android.sdk.iap.lib.service;
import android.content.Context;
import android.util.Log;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
import java.util.ArrayList;
/**
* Created by sangbum7.kim on 2018-02-28.
*/
public class ConsumePurchasedItems extends BaseService {
private static final String TAG = ConsumePurchasedItems.class.getSimpleName();
private OnConsumePurchasedItemsListener mOnConsumePurchasedItemsListener = null;
private static String mPurchaseIds = "";
protected ArrayList<ConsumeVo> mConsumeList = null;
public ConsumePurchasedItems(IapHelper _iapHelper, Context _context, OnConsumePurchasedItemsListener _onConsumePurchasedItemsListener) {
super(_iapHelper, _context);
mOnConsumePurchasedItemsListener = _onConsumePurchasedItemsListener;
}
public static void setPurchaseIds(String _purchaseIds) {
mPurchaseIds = _purchaseIds;
}
public void setConsumeList(ArrayList<ConsumeVo> _consumeList) {
this.mConsumeList = _consumeList;
}
@Override
public void runServiceProcess() {
if (mIapHelper != null) {
if (mIapHelper.safeConsumePurchasedItems(ConsumePurchasedItems.this,
mPurchaseIds,
mIapHelper.getShowErrorDialog()) == true) {
return;
}
}
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
onEndProcess();
}
@Override
public void onReleaseProcess() {
Log.i(TAG, "ConsumePurchasedItems.onReleaseProcess");
try {
if (mOnConsumePurchasedItemsListener != null) {
mOnConsumePurchasedItemsListener.onConsumePurchasedItems(mErrorVo, mConsumeList);
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}

View File

@@ -0,0 +1,63 @@
package com.samsung.android.sdk.iap.lib.service;
import android.content.Context;
import android.util.Log;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
import java.util.ArrayList;
/**
* Created by sangbum7.kim on 2018-02-28.
*/
public class OwnedProduct extends BaseService {
private static final String TAG = OwnedProduct.class.getSimpleName();
private OnGetOwnedListListener mOnGetOwnedListListener = null;
private static String mProductType = "";
protected ArrayList<OwnedProductVo> mOwnedList = null;
public OwnedProduct(IapHelper _iapHelper, Context _context, OnGetOwnedListListener _onGetOwnedListListener) {
super(_iapHelper, _context);
mOnGetOwnedListListener = _onGetOwnedListListener;
}
public static void setProductType(String _productType) {
mProductType = _productType;
}
public void setOwnedList(ArrayList<OwnedProductVo> _ownedList) {
this.mOwnedList = _ownedList;
}
@Override
public void runServiceProcess() {
Log.i(TAG, "runServiceProcess");
if (mIapHelper != null) {
if (mIapHelper.safeGetOwnedList(OwnedProduct.this,
mProductType,
mIapHelper.getShowErrorDialog()) == true) {
return;
}
}
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
onEndProcess();
}
@Override
public void onReleaseProcess() {
Log.i(TAG, "OwnedProduct.onReleaseProcess");
try {
if (mOnGetOwnedListListener != null) {
mOnGetOwnedListListener.onGetOwnedProducts(mErrorVo, mOwnedList);
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}

View File

@@ -0,0 +1,63 @@
package com.samsung.android.sdk.iap.lib.service;
import android.content.Context;
import android.util.Log;
import com.samsung.android.sdk.iap.lib.R;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.listener.OnGetProductsDetailsListener;
import com.samsung.android.sdk.iap.lib.vo.ProductVo;
import java.util.ArrayList;
/**
* Created by sangbum7.kim on 2018-02-28.
*/
public class ProductsDetails extends BaseService {
private static final String TAG = ProductsDetails.class.getSimpleName();
private OnGetProductsDetailsListener mOnGetProductsDetailsListener = null;
private static String mProductIds = "";
protected ArrayList<ProductVo> mProductsDetails = null;
public ProductsDetails(IapHelper _iapHelper, Context _context, OnGetProductsDetailsListener _onGetProductsDetailsListener) {
super(_iapHelper, _context);
mOnGetProductsDetailsListener = _onGetProductsDetailsListener;
}
public static void setProductId(String _productIds) {
mProductIds = _productIds;
}
public void setProductsDetails(ArrayList<ProductVo> _ProductsDetails) {
this.mProductsDetails = _ProductsDetails;
}
@Override
public void runServiceProcess() {
Log.i(TAG, "succeedBind");
if (mIapHelper != null) {
if (mIapHelper.safeGetProductsDetails(ProductsDetails.this,
mProductIds,
mIapHelper.getShowErrorDialog()) == true) {
return;
}
}
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
onEndProcess();
}
@Override
public void onReleaseProcess() {
Log.i(TAG, "OwnedProduct.onEndProcess");
try {
if (mOnGetProductsDetailsListener != null) {
mOnGetProductsDetailsListener.onGetProducts(mErrorVo, mProductsDetails);
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}

View File

@@ -0,0 +1,146 @@
package com.samsung.android.sdk.iap.lib.vo;
import android.text.format.DateFormat;
import org.json.JSONException;
import org.json.JSONObject;
public class BaseVo {
private String mItemId;
private String mItemName;
private Double mItemPrice;
private String mItemPriceString;
private String mCurrencyUnit;
private String mCurrencyCode;
private String mItemDesc;
private String mType;
private Boolean mIsConsumable;
public BaseVo() {
}
public BaseVo(String _jsonString) {
try {
JSONObject jObject = new JSONObject(_jsonString);
setItemId(jObject.optString("mItemId"));
setItemName(jObject.optString("mItemName"));
setItemPrice(jObject.optDouble("mItemPrice"));
setItemPriceString(jObject.optString("mItemPriceString"));
setCurrencyUnit(jObject.optString("mCurrencyUnit"));
setCurrencyCode(jObject.optString("mCurrencyCode"));
setItemDesc(jObject.optString("mItemDesc"));
setType(jObject.optString("mType"));
Boolean isConsumable = false;
if (jObject.optString("mConsumableYN") != null && jObject.optString("mConsumableYN").equals("Y")) {
isConsumable = true;
}
setIsConsumable(isConsumable);
} catch (JSONException e) {
e.printStackTrace();
}
}
public String getItemId() {
return mItemId;
}
public void setItemId(String _itemId) {
mItemId = _itemId;
}
public String getItemName() {
return mItemName;
}
public void setItemName(String _itemName) {
mItemName = _itemName;
}
public Double getItemPrice() {
return mItemPrice;
}
public void setItemPrice(Double _itemPrice) {
mItemPrice = _itemPrice;
}
public String getItemPriceString() {
return mItemPriceString;
}
public void setItemPriceString(String _itemPriceString) {
mItemPriceString = _itemPriceString;
}
public String getCurrencyUnit() {
return mCurrencyUnit;
}
public void setCurrencyUnit(String _currencyUnit) {
mCurrencyUnit = _currencyUnit;
}
public String getCurrencyCode() {
return mCurrencyCode;
}
public void setCurrencyCode(String _currencyCode) {
mCurrencyCode = _currencyCode;
}
public String getItemDesc() {
return mItemDesc;
}
public void setItemDesc(String _itemDesc) {
mItemDesc = _itemDesc;
}
public String getType() {
return mType;
}
public void setType(String _itemDesc) {
mType = _itemDesc;
}
public Boolean getIsConsumable() {
return mIsConsumable;
}
public void setIsConsumable(Boolean _consumableYN) {
mIsConsumable = _consumableYN;
}
public String dump() {
String dump = null;
dump = "ItemId : " + getItemId() + "\n" +
"ItemName : " + getItemName() + "\n" +
"ItemPrice : " + getItemPrice() + "\n" +
"ItemPriceString : " + getItemPriceString() + "\n" +
"ItemDesc : " + getItemDesc() + "\n" +
"CurrencyUnit : " + getCurrencyUnit() + "\n" +
"CurrencyCode : " + getCurrencyCode() + "\n" +
"IsConsumable : " + getIsConsumable() + "\n" +
"Type : " + getType();
return dump;
}
protected String getDateString(long _timeMills) {
String result = "";
String dateFormat = "yyyy-MM-dd HH:mm:ss";
try {
result = DateFormat.format(dateFormat, _timeMills).toString();
} catch (Exception e) {
e.printStackTrace();
result = "";
}
return result;
}
}

View File

@@ -0,0 +1,75 @@
package com.samsung.android.sdk.iap.lib.vo;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
public class ConsumeVo {
private static final String TAG = ConsumeVo.class.getSimpleName();
private String mPurchaseId;
private String mStatusString;
private int mStatusCode;
private String mJsonString = "";
public ConsumeVo(String _jsonString) {
setJsonString(_jsonString);
try {
JSONObject jObject = new JSONObject(_jsonString);
Log.i(TAG, jObject.toString(4));
setPurchaseId(jObject.optString("mPurchaseId"));
setStatusString(jObject.optString("mStatusString"));
setStatusCode(jObject.optInt("mStatusCode"));
} catch (JSONException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public String getPurchaseId() {
return mPurchaseId;
}
public void setPurchaseId(String _paymentId) {
mPurchaseId = _paymentId;
}
public String getStatusString() {
return mStatusString;
}
public void setStatusString(String _statusString) {
mStatusString = _statusString;
}
public int getStatusCode() {
return mStatusCode;
}
public void setStatusCode(int _statusCode) {
mStatusCode = _statusCode;
}
public String getJsonString() {
return mJsonString;
}
public void setJsonString(String _jsonString) {
mJsonString = _jsonString;
}
public String dump() {
String dump = null;
dump = "PurchaseId : " + getPurchaseId() + "\n" +
"StatusString : " + getStatusString() + "\n" +
"StatusCode : " + getStatusCode();
return dump;
}
}

View File

@@ -0,0 +1,59 @@
package com.samsung.android.sdk.iap.lib.vo;
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
public class ErrorVo {
private int mErrorCode = HelperDefine.IAP_PAYMENT_IS_CANCELED;
private String mErrorString = "";
private String mErrorDetailsString = "";
private String mExtraString = "";
private boolean mShowDialog = false;
public void setError(int _errorCode, String _errorString) {
mErrorCode = _errorCode;
mErrorString = _errorString;
}
public void setError(int _errorCode, String _errorString, String _errorDetails) {
mErrorCode = _errorCode;
mErrorString = _errorString;
mErrorDetailsString = _errorDetails;
}
public int getErrorCode() {
return mErrorCode;
}
public String getErrorString() {
return mErrorString;
}
public String getErrorDetailsString() {
return mErrorDetailsString;
}
public String getExtraString() {
return mExtraString;
}
public void setExtraString(String _extraString) {
mExtraString = _extraString;
}
public boolean isShowDialog() {
return mShowDialog;
}
public void setShowDialog(boolean _showDialog) {
mShowDialog = _showDialog;
}
public String dump() {
String dump =
"ErrorCode : " + getErrorCode() + "\n" +
"ErrorString : " + getErrorString() + "\n" +
"ErrorDetailsString : " + getErrorDetailsString() + "\n" +
"ExtraString : " + getExtraString();
return dump;
}
}

View File

@@ -0,0 +1,115 @@
package com.samsung.android.sdk.iap.lib.vo;
import android.util.Base64;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
public class OwnedProductVo extends BaseVo {
private static final String TAG = OwnedProductVo.class.getSimpleName();
private String mPaymentId;
private String mPurchaseId;
private String mPurchaseDate;
private String mPassThroughParam;
// Expiration date for a item which is "subscription" type
// ========================================================================
private String mSubscriptionEndDate = "";
// ========================================================================
private String mJsonString = "";
public OwnedProductVo() {
}
public OwnedProductVo(String _jsonString) {
super(_jsonString);
setJsonString(_jsonString);
try {
JSONObject jObject = new JSONObject(_jsonString);
setPaymentId(jObject.optString("mPaymentId"));
setPurchaseId(jObject.optString("mPurchaseId"));
setPurchaseDate(getDateString(jObject.optLong("mPurchaseDate")));
jObject.remove("mPurchaseDate");
jObject.put("mPurchaseDate", getPurchaseDate());
String decodedPassThroughParam = new String(Base64.decode(jObject.optString("mPassThroughParam"), 0), "UTF-8");
setPassThroughParam(decodedPassThroughParam);
if (jObject.optLong("mSubscriptionEndDate") != 0) {
setSubscriptionEndDate(getDateString(jObject.optLong("mSubscriptionEndDate")));
}
jObject.remove("mSubscriptionEndDate");
jObject.put("mSubscriptionEndDate", getSubscriptionEndDate());
setJsonString(jObject.toString());
} catch (JSONException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public String getPaymentId() {
return mPaymentId;
}
public void setPaymentId(String _paymentId) {
mPaymentId = _paymentId;
}
public String getPurchaseId() {
return mPurchaseId;
}
public void setPurchaseId(String _purchaseId) {
mPurchaseId = _purchaseId;
}
public String getPurchaseDate() {
return mPurchaseDate;
}
public void setPurchaseDate(String _purchaseDate) {
mPurchaseDate = _purchaseDate;
}
public String getSubscriptionEndDate() {
return mSubscriptionEndDate;
}
public void setSubscriptionEndDate(String _subscriptionEndDate) {
mSubscriptionEndDate = _subscriptionEndDate;
}
public String getPassThroughParam() {
return mPassThroughParam;
}
public void setPassThroughParam(String _passThroughParam) {
mPassThroughParam = _passThroughParam;
}
public String getJsonString() {
return mJsonString;
}
public void setJsonString(String _jsonString) {
mJsonString = _jsonString;
}
public String dump() {
String dump = super.dump() + "\n";
dump += "PaymentID : " + getPaymentId() + "\n" +
"PurchaseID : " + getPurchaseId() + "\n" +
"PurchaseDate : " + getPurchaseDate() + "\n" +
"PassThroughParam : " + getPassThroughParam() + "\n" +
"SubscriptionEndDate : " + getSubscriptionEndDate();
return dump;
}
}

View File

@@ -0,0 +1,223 @@
package com.samsung.android.sdk.iap.lib.vo;
import org.json.JSONException;
import org.json.JSONObject;
public class ProductVo extends BaseVo {
private static final String TAG = ProductVo.class.getSimpleName();
//Subscription data
private String mSubscriptionDurationUnit;
private String mSubscriptionDurationMultiplier;
// Tiered Subscription data
private String mTieredPrice = "";
private String mTieredPriceString = "";
private String mTieredSubscriptionYN = "";
private String mTieredSubscriptionDurationUnit = "";
private String mTieredSubscriptionDurationMultiplier = "";
private String mTieredSubscriptionCount = "";
private String mShowStartDate = "";
private String mShowEndDate = "";
private String mItemImageUrl;
private String mItemDownloadUrl;
private String mReserved1;
private String mReserved2;
private String mFreeTrialPeriod;
private String mJsonString;
public ProductVo() {
}
public ProductVo(String _jsonString) {
super(_jsonString);
setJsonString(_jsonString);
try {
JSONObject jObject = new JSONObject(_jsonString);
setSubscriptionDurationUnit(jObject.optString("mSubscriptionDurationUnit"));
setSubscriptionDurationMultiplier(jObject.optString("mSubscriptionDurationMultiplier"));
setTieredSubscriptionYN(jObject.optString("mTieredSubscriptionYN"));
setTieredSubscriptionDurationUnit(jObject.optString("mTieredSubscriptionDurationUnit"));
setTieredSubscriptionDurationMultiplier(jObject.optString("mTieredSubscriptionDurationMultiplier"));
setTieredSubscriptionCount(jObject.optString("mTieredSubscriptionCount"));
setTieredPrice(jObject.optString("mTieredPrice"));
setTieredPriceString(jObject.optString("mTieredPriceString"));
setShowStartDate(getDateString(jObject.optLong("mShowStartDate")));
setShowEndDate(getDateString(jObject.optLong("mShowEndDate")));
setItemImageUrl(jObject.optString("mItemImageUrl"));
setItemDownloadUrl(jObject.optString("mItemDownloadUrl"));
setReserved1(jObject.optString("mReserved1"));
setReserved2(jObject.optString("mReserved2"));
setFreeTrialPeriod(jObject.optString("mFreeTrialPeriod"));
} catch (JSONException e) {
e.printStackTrace();
}
}
public String getSubscriptionDurationUnit() {
return mSubscriptionDurationUnit;
}
public void setSubscriptionDurationUnit(String _subscriptionDurationUnit) {
mSubscriptionDurationUnit = _subscriptionDurationUnit;
}
public String getSubscriptionDurationMultiplier() {
return mSubscriptionDurationMultiplier;
}
public void setSubscriptionDurationMultiplier(
String _subscriptionDurationMultiplier) {
mSubscriptionDurationMultiplier = _subscriptionDurationMultiplier;
}
public String getTieredSubscriptionYN() {
return mTieredSubscriptionYN;
}
public void setTieredSubscriptionYN(String _tieredSubscriptionYN) {
this.mTieredSubscriptionYN = _tieredSubscriptionYN;
}
public String getTieredPrice() {
return mTieredPrice;
}
public void setTieredPrice(String _tieredPrice) {
this.mTieredPrice = _tieredPrice;
}
public String getTieredPriceString() {
return mTieredPriceString;
}
public void setTieredPriceString(String _tieredPriceString) {
this.mTieredPriceString = _tieredPriceString;
}
public String getTieredSubscriptionDurationUnit() {
return mTieredSubscriptionDurationUnit;
}
public void setTieredSubscriptionDurationUnit(String _tieredSubscriptionDurationUnit) {
this.mTieredSubscriptionDurationUnit = _tieredSubscriptionDurationUnit;
}
public String getTieredSubscriptionDurationMultiplier() {
return mTieredSubscriptionDurationMultiplier;
}
public void setTieredSubscriptionDurationMultiplier(String _tieredSubscriptionDurationMultiplier) {
this.mTieredSubscriptionDurationMultiplier = _tieredSubscriptionDurationMultiplier;
}
public String getTieredSubscriptionCount() {
return mTieredSubscriptionCount;
}
public void setTieredSubscriptionCount(String _tieredSubscriptionCount) {
this.mTieredSubscriptionCount = _tieredSubscriptionCount;
}
public String getShowStartDate() {
return mShowStartDate;
}
public void setShowStartDate(String showStartDate) {
this.mShowStartDate = showStartDate;
}
public String getShowEndDate() {
return mShowEndDate;
}
public void setShowEndDate(String showEndDate) {
this.mShowEndDate = showEndDate;
}
public String getItemImageUrl() {
return mItemImageUrl;
}
public void setItemImageUrl(String _itemImageUrl) {
mItemImageUrl = _itemImageUrl;
}
public String getItemDownloadUrl() {
return mItemDownloadUrl;
}
public void setItemDownloadUrl(String _itemDownloadUrl) {
mItemDownloadUrl = _itemDownloadUrl;
}
public String getReserved1() {
return mReserved1;
}
public void setReserved1(String _reserved1) {
mReserved1 = _reserved1;
}
public String getReserved2() {
return mReserved2;
}
public void setReserved2(String _reserved2) {
mReserved2 = _reserved2;
}
public String getFreeTrialPeriod() {
return mFreeTrialPeriod;
}
public void setFreeTrialPeriod(String _freeTrialPeriod) {
mFreeTrialPeriod = _freeTrialPeriod;
}
public String getJsonString() {
return mJsonString;
}
public void setJsonString(String _jsonString) {
mJsonString = _jsonString;
}
public String tieredDump() {
String dump = "";
if (getTieredSubscriptionYN().equals("Y") == true) {
dump = "TieredSubscriptionYN : " + getTieredSubscriptionYN() + "\n" +
"TieredPrice : " + getTieredPrice() + "\n" +
"TieredPriceString : " + getTieredPriceString() + "\n" +
"TieredSubscriptionCount : " + getTieredSubscriptionCount() + "\n" +
"TieredSubscriptionDurationUnit : " + getTieredSubscriptionDurationUnit() + "\n" +
"TieredSubscriptionDurationMultiplier : " + getTieredSubscriptionDurationMultiplier() + "\n" +
"ShowStartDate : " + getShowStartDate() + "\n" +
"ShowEndDate : " + getShowEndDate();
}
return dump;
}
public String dump() {
String dump = super.dump() + "\n";
dump += "SubscriptionDurationUnit : "
+ getSubscriptionDurationUnit() + "\n" +
"SubscriptionDurationMultiplier : " +
getSubscriptionDurationMultiplier() + "\n" +
"ItemImageUrl : " + getItemImageUrl() + "\n" +
"ItemDownloadUrl : " + getItemDownloadUrl() + "\n" +
"Reserved1 : " + getReserved1() + "\n" +
"Reserved2 : " + getReserved2() + "\n" +
"FreeTrialPeriod : " + getFreeTrialPeriod() + "\n" +
tieredDump();
return dump;
}
}

View File

@@ -0,0 +1,172 @@
package com.samsung.android.sdk.iap.lib.vo;
import android.util.Base64;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
public class PurchaseVo extends BaseVo {
private static final String TAG = PurchaseVo.class.getSimpleName();
private String mPaymentId;
private String mPurchaseId;
private String mPurchaseDate;
private String mVerifyUrl;
private String mPassThroughParam;
private String mItemImageUrl;
private String mItemDownloadUrl;
private String mReserved1;
private String mReserved2;
private String mOrderId;
private String mUdpSignature;
private String mJsonString;
public PurchaseVo(String _jsonString) {
super(_jsonString);
setJsonString(_jsonString);
try {
JSONObject jObject = new JSONObject(_jsonString);
setPaymentId(jObject.optString("mPaymentId"));
setPurchaseId(jObject.optString("mPurchaseId"));
setPurchaseDate(getDateString(jObject.optLong("mPurchaseDate")));
jObject.remove("mPurchaseDate");
jObject.put("mPurchaseDate", getPurchaseDate());
String decodedPassThroughParam = new String(Base64.decode(jObject.optString("mPassThroughParam"), 0), "UTF-8");
setPassThroughParam(decodedPassThroughParam);
setItemImageUrl(jObject.optString("mItemImageUrl"));
setItemDownloadUrl(jObject.optString("mItemDownloadUrl"));
setReserved1(jObject.optString("mReserved1"));
setReserved2(jObject.optString("mReserved2"));
setOrderId(jObject.optString("mOrderId"));
setVerifyUrl(jObject.optString("mVerifyUrl"));
setUdpSignature(jObject.optString("mUdpSignature"));
setJsonString(jObject.toString());
} catch (JSONException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public String getPaymentId() {
return mPaymentId;
}
public void setPaymentId(String _paymentId) {
mPaymentId = _paymentId;
}
public String getPurchaseId() {
return mPurchaseId;
}
public void setPurchaseId(String _purchaseId) {
mPurchaseId = _purchaseId;
}
public String getPurchaseDate() {
return mPurchaseDate;
}
public void setPurchaseDate(String _purchaseDate) {
mPurchaseDate = _purchaseDate;
}
public String getVerifyUrl() {
return mVerifyUrl;
}
public void setVerifyUrl(String _verifyUrl) {
mVerifyUrl = _verifyUrl;
}
public String getPassThroughParam() {
return mPassThroughParam;
}
public void setPassThroughParam(String _passThroughParam) {
mPassThroughParam = _passThroughParam;
}
public String getItemImageUrl() {
return mItemImageUrl;
}
public void setItemImageUrl(String _itemImageUrl) {
mItemImageUrl = _itemImageUrl;
}
public String getItemDownloadUrl() {
return mItemDownloadUrl;
}
public void setItemDownloadUrl(String _itemDownloadUrl) {
mItemDownloadUrl = _itemDownloadUrl;
}
public String getReserved1() {
return mReserved1;
}
public void setReserved1(String _reserved1) {
mReserved1 = _reserved1;
}
public String getReserved2() {
return mReserved2;
}
public void setReserved2(String _reserved2) {
mReserved2 = _reserved2;
}
public String getOrderId() {
return mOrderId;
}
public void setOrderId(String orderId) {
this.mOrderId = orderId;
}
public String getUdpSignature() {
return mUdpSignature;
}
public void setUdpSignature(String udpSignature) {
this.mUdpSignature = udpSignature;
}
public String getJsonString() {
return mJsonString;
}
public void setJsonString(String _jsonString) {
mJsonString = _jsonString;
}
public String dump() {
String dump = super.dump() + "\n";
dump += "PaymentID : " + getPaymentId() + "\n" +
"PurchaseId : " + getPurchaseId() + "\n" +
"PurchaseDate : " + getPurchaseDate() + "\n" +
"PassThroughParam : " + getPassThroughParam() + "\n" +
"VerifyUrl : " + getVerifyUrl() + "\n" +
"ItemImageUrl : " + getItemImageUrl() + "\n" +
"ItemDownloadUrl : " + getItemDownloadUrl() + "\n" +
"Reserved1 : " + getReserved1() + "\n" +
"Reserved2 : " + getReserved2() + "\n" +
"UdpSignature : " + getUdpSignature();
return dump;
}
}

View File

@@ -52,8 +52,4 @@
<string name="mids_sapps_header_update_galaxy_apps">Update Galaxy Apps</string>
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">A new version is available. Galaxy Apps will be updated to the latest version to complete this purchase.</string>
<string name="pay_cancel">Payment cancellation</string>
<string name="pay_suc">Payment successful</string>
<string name="pay_fail">Payment failed</string>
</resources>

View File

@@ -52,8 +52,4 @@
<string name="mids_sapps_header_update_galaxy_apps">更新三星应用商店</string>
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">有新版本可用。三星应用商店将更新至最新版本以完成本次购物。</string>
<string name="pay_cancel">支付取消</string>
<string name="pay_suc">支付成功</string>
<string name="pay_fail">支付失敗</string>
</resources>

View File

@@ -52,8 +52,4 @@
<string name="mids_sapps_header_update_galaxy_apps">更新 Galaxy Apps</string>
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">已有新版本可用。Galaxy Apps 將更新至最新版本以完成此購買。</string>
<string name="pay_cancel">支付取消</string>
<string name="pay_suc">支付成功</string>
<string name="pay_fail">支付失敗</string>
</resources>

View File

@@ -52,8 +52,4 @@
<string name="mids_sapps_header_update_galaxy_apps">更新 Galaxy Apps</string>
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">有可用的新版本。Galaxy Apps 將更新至最新版本以完成此筆購買。</string>
<string name="pay_cancel">支付取消</string>
<string name="pay_suc">支付成功</string>
<string name="pay_fail">支付失敗</string>
</resources>

View File

@@ -20,7 +20,5 @@
<string name="ids_com_body_error_code_c" did="IDS_COM_BODY_ERROR_CODE_C">Error code:</string>
<!-- unused -->
<string name="mids_sapps_pop_to_purchase_items_you_need_to_install_samsung_in_app_purchase_install_q" did="MIDS_SAPPS_POP_TO_PURCHASE_ITEMS_YOU_NEED_TO_INSTALL_SAMSUNG_IN_APP_PURCHASE_INSTALL_Q">To purchase items, you need to install Samsung In-App Purchase. Install?</string>
<string name="pay_cancel">Payment cancellation</string>
<string name="pay_suc">Payment successful</string>
<string name="pay_fail">Payment failed</string>
</resources>

View File

@@ -1,2 +0,0 @@
/build
*.iml

View File

@@ -1,37 +0,0 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
}
compileOptions {
kotlinOptions.freeCompilerArgs += ['-module-name', "com.opensource.svgaplayer"]
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/MANIFEST.MF'
}
}
dependencies {
implementation 'com.squareup.wire:wire-runtime:4.4.1'
}

View File

@@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/PonyCui_Home/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -1,8 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.opensource.svgaplayer">
<application android:allowBackup="true" android:label="@string/app_name">
</application>
</manifest>

View File

@@ -1,9 +0,0 @@
package com.opensource.svgaplayer
/**
* Created by miaojun on 2019/6/21.
* mail:1290846731@qq.com
*/
interface IClickAreaListener{
fun onResponseArea(key : String,x0 : Int, y0 : Int, x1 : Int, y1 : Int)
}

View File

@@ -1,119 +0,0 @@
package com.opensource.svgaplayer
import android.content.Context
import com.opensource.svgaplayer.utils.log.LogUtils
import java.io.File
import java.net.URL
import java.security.MessageDigest
/**
* SVGA 缓存管理
*/
object SVGACache {
enum class Type {
DEFAULT,
FILE
}
private const val TAG = "SVGACache"
private var type: Type = Type.DEFAULT
private var cacheDir: String = "/"
get() {
if (field != "/") {
val dir = File(field)
if (!dir.exists()) {
dir.mkdirs()
}
}
return field
}
fun onCreate(context: Context?) {
onCreate(context, Type.DEFAULT)
}
fun onCreate(context: Context?, type: Type) {
if (isInitialized()) return
context ?: return
cacheDir = "${context.cacheDir.absolutePath}/svga/"
File(cacheDir).takeIf { !it.exists() }?.mkdirs()
this.type = type
}
/**
* 清理缓存
*/
fun clearCache() {
if (!isInitialized()) {
LogUtils.error(TAG, "SVGACache is not init!")
return
}
SVGAParser.threadPoolExecutor.execute {
clearDir(cacheDir)
LogUtils.info(TAG, "Clear svga cache done!")
}
}
// 清除目录下的所有文件
internal fun clearDir(path: String) {
try {
val dir = File(path)
dir.takeIf { it.exists() }?.let { parentDir ->
parentDir.listFiles()?.forEach { file ->
if (!file.exists()) {
return@forEach
}
if (file.isDirectory) {
clearDir(file.absolutePath)
}
file.delete()
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "Clear svga cache path: $path fail", e)
}
}
fun isInitialized(): Boolean {
return "/" != cacheDir && File(cacheDir).exists()
}
fun isDefaultCache(): Boolean = type == Type.DEFAULT
fun isCached(cacheKey: String): Boolean {
return if (isDefaultCache()) {
buildCacheDir(cacheKey)
} else {
buildSvgaFile(
cacheKey
)
}.exists()
}
fun buildCacheKey(str: String): String {
val messageDigest = MessageDigest.getInstance("MD5")
messageDigest.update(str.toByteArray(charset("UTF-8")))
val digest = messageDigest.digest()
var sb = ""
for (b in digest) {
sb += String.format("%02x", b)
}
return sb
}
fun buildCacheKey(url: URL): String = buildCacheKey(url.toString())
fun buildCacheDir(cacheKey: String): File {
return File("$cacheDir$cacheKey/")
}
fun buildSvgaFile(cacheKey: String): File {
return File("$cacheDir$cacheKey.svga")
}
fun buildAudioFile(audio: String): File {
return File("$cacheDir$audio.mp3")
}
}

View File

@@ -1,13 +0,0 @@
package com.opensource.svgaplayer
/**
* Created by cuiminghui on 2017/3/30.
*/
interface SVGACallback {
fun onPause()
fun onFinished()
fun onRepeat()
fun onStep(frame: Int, percentage: Double)
}

View File

@@ -1,9 +0,0 @@
package com.opensource.svgaplayer
/**
* Created by miaojun on 2019/6/21.
* mail:1290846731@qq.com
*/
interface SVGAClickAreaListener{
fun onClick(clickKey : String)
}

View File

@@ -1,106 +0,0 @@
package com.opensource.svgaplayer
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.widget.ImageView
import com.opensource.svgaplayer.drawer.SVGACanvasDrawer
class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity): Drawable() {
constructor(videoItem: SVGAVideoEntity): this(videoItem, SVGADynamicEntity())
var cleared = true
internal set (value) {
if (field == value) {
return
}
field = value
invalidateSelf()
}
var currentFrame = 0
internal set (value) {
if (field == value) {
return
}
field = value
invalidateSelf()
}
var scaleType: ImageView.ScaleType = ImageView.ScaleType.MATRIX
private val drawer = SVGACanvasDrawer(videoItem, dynamicItem)
override fun draw(canvas: Canvas?) {
if (cleared) {
return
}
canvas?.let {
drawer.drawFrame(it,currentFrame, scaleType)
}
}
override fun setAlpha(alpha: Int) {
}
override fun getOpacity(): Int {
return PixelFormat.TRANSPARENT
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
fun resume() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.resume(it)
}else{
videoItem.soundPool?.resume(it)
}
}
}
}
fun pause() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.pause(it)
}else{
videoItem.soundPool?.pause(it)
}
}
}
}
fun stop() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.stop(it)
}else{
videoItem.soundPool?.stop(it)
}
}
}
}
fun clear() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.stop(it)
}else{
videoItem.soundPool?.stop(it)
}
}
audio.playID = null
}
videoItem.clear()
}
}

View File

@@ -1,153 +0,0 @@
package com.opensource.svgaplayer
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.text.BoringLayout
import android.text.StaticLayout
import android.text.TextPaint
import java.net.HttpURLConnection
import java.net.URL
/**
* Created by cuiminghui on 2017/3/30.
*/
class SVGADynamicEntity {
internal var dynamicHidden: HashMap<String, Boolean> = hashMapOf()
internal var dynamicImage: HashMap<String, Bitmap> = hashMapOf()
internal var dynamicText: HashMap<String, String> = hashMapOf()
internal var dynamicTextPaint: HashMap<String, TextPaint> = hashMapOf()
internal var dynamicStaticLayoutText: HashMap<String, StaticLayout> = hashMapOf()
internal var dynamicBoringLayoutText: HashMap<String, BoringLayout> = hashMapOf()
internal var dynamicDrawer: HashMap<String, (canvas: Canvas, frameIndex: Int) -> Boolean> = hashMapOf()
//点击事件回调map
internal var mClickMap : HashMap<String, IntArray> = hashMapOf()
internal var dynamicIClickArea: HashMap<String, IClickAreaListener> = hashMapOf()
internal var dynamicDrawerSized: HashMap<String, (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean> = hashMapOf()
internal var isTextDirty = false
fun setHidden(value: Boolean, forKey: String) {
this.dynamicHidden.put(forKey, value)
}
fun setDynamicImage(bitmap: Bitmap, forKey: String) {
this.dynamicImage.put(forKey, bitmap)
}
fun setDynamicImage(url: String, forKey: String) {
val handler = android.os.Handler()
SVGAParser.threadPoolExecutor.execute {
(URL(url).openConnection() as? HttpURLConnection)?.let {
try {
it.connectTimeout = 20 * 1000
it.requestMethod = "GET"
it.connect()
it.inputStream.use { stream ->
BitmapFactory.decodeStream(stream)?.let {
handler.post { setDynamicImage(it, forKey) }
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
it.disconnect()
} catch (disconnectException: Throwable) {
// ignored here
}
}
}
}
}
fun setDynamicText(text: String, textPaint: TextPaint, forKey: String) {
this.isTextDirty = true
this.dynamicText.put(forKey, text)
this.dynamicTextPaint.put(forKey, textPaint)
}
fun setDynamicText(layoutText: StaticLayout, forKey: String) {
this.isTextDirty = true
this.dynamicStaticLayoutText.put(forKey, layoutText)
}
fun setDynamicText(layoutText: BoringLayout, forKey: String) {
this.isTextDirty = true
BoringLayout.isBoring(layoutText.text,layoutText.paint)?.let {
this.dynamicBoringLayoutText.put(forKey,layoutText)
}
}
fun setDynamicDrawer(drawer: (canvas: Canvas, frameIndex: Int) -> Boolean, forKey: String) {
this.dynamicDrawer.put(forKey, drawer)
}
fun setClickArea(clickKey: List<String>) {
for(itemKey in clickKey){
dynamicIClickArea.put(itemKey,object : IClickAreaListener {
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
mClickMap.let {
if(it.get(key) == null){
it.put(key, intArrayOf(x0,y0,x1,y1))
}else{
it.get(key)?.let {
it[0] = x0
it[1] = y0
it[2] = x1
it[3] = y1
}
}
}
}
})
}
}
fun setClickArea(clickKey: String) {
dynamicIClickArea.put(clickKey, object : IClickAreaListener {
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
mClickMap.let {
if (it.get(key) == null) {
it.put(key, intArrayOf(x0, y0, x1, y1))
} else {
it.get(key)?.let {
it[0] = x0
it[1] = y0
it[2] = x1
it[3] = y1
}
}
}
}
})
}
fun setDynamicDrawerSized(drawer: (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean, forKey: String) {
this.dynamicDrawerSized.put(forKey, drawer)
}
fun clearDynamicObjects() {
this.isTextDirty = true
this.dynamicHidden.clear()
this.dynamicImage.clear()
this.dynamicText.clear()
this.dynamicTextPaint.clear()
this.dynamicStaticLayoutText.clear()
this.dynamicBoringLayoutText.clear()
this.dynamicDrawer.clear()
this.dynamicIClickArea.clear()
this.mClickMap.clear()
this.dynamicDrawerSized.clear()
}
}

View File

@@ -1,329 +0,0 @@
package com.opensource.svgaplayer
import android.animation.Animator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import com.opensource.svgaplayer.utils.SVGARange
import com.opensource.svgaplayer.utils.log.LogUtils
import java.lang.ref.WeakReference
import java.net.URL
/**
* Created by PonyCui on 2017/3/29.
*/
open class SVGAImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private val TAG = "SVGAImageView"
enum class FillMode {
Backward,
Forward,
Clear,
}
var isAnimating = false
private set
var loops = 0
@Deprecated(
"It is recommended to use clearAfterDetached, or manually call to SVGAVideoEntity#clear." +
"If you just consider cleaning up the canvas after playing, you can use FillMode#Clear.",
level = DeprecationLevel.WARNING
)
var clearsAfterStop = false
var clearsAfterDetached = false
var fillMode: FillMode = FillMode.Forward
var callback: SVGACallback? = null
private var mAnimator: ValueAnimator? = null
private var mItemClickAreaListener: SVGAClickAreaListener? = null
private var mAntiAlias = true
private var mAutoPlay = true
private val mAnimatorListener = AnimatorListener(this)
private val mAnimatorUpdateListener = AnimatorUpdateListener(this)
private var mStartFrame = 0
private var mEndFrame = 0
init {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}
attrs?.let { loadAttrs(it) }
}
private fun loadAttrs(attrs: AttributeSet) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SVGAImageView, 0, 0)
loops = typedArray.getInt(R.styleable.SVGAImageView_loopCount, 0)
clearsAfterStop = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterStop, false)
clearsAfterDetached = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterDetached, false)
mAntiAlias = typedArray.getBoolean(R.styleable.SVGAImageView_antiAlias, true)
mAutoPlay = typedArray.getBoolean(R.styleable.SVGAImageView_autoPlay, true)
typedArray.getString(R.styleable.SVGAImageView_fillMode)?.let {
when (it) {
"0" -> {
fillMode = FillMode.Backward
}
"1" -> {
fillMode = FillMode.Forward
}
"2" -> {
fillMode = FillMode.Clear
}
}
}
typedArray.getString(R.styleable.SVGAImageView_source)?.let {
parserSource(it)
}
typedArray.recycle()
}
private fun parserSource(source: String) {
val refImgView = WeakReference<SVGAImageView>(this)
val parser = SVGAParser(context)
if (source.startsWith("http://") || source.startsWith("https://")) {
parser.decodeFromURL(URL(source), createParseCompletion(refImgView))
} else {
parser.decodeFromAssets(source, createParseCompletion(refImgView))
}
}
private fun createParseCompletion(ref: WeakReference<SVGAImageView>): SVGAParser.ParseCompletion {
return object : SVGAParser.ParseCompletion {
override fun onComplete(videoItem: SVGAVideoEntity) {
ref.get()?.startAnimation(videoItem)
}
override fun onError() {}
}
}
private fun startAnimation(videoItem: SVGAVideoEntity) {
this@SVGAImageView.post {
videoItem.antiAlias = mAntiAlias
setVideoItem(videoItem)
getSVGADrawable()?.scaleType = scaleType
if (mAutoPlay) {
startAnimation()
}
}
}
fun startAnimation() {
startAnimation(null, false)
}
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
stopAnimation(false)
play(range, reverse)
}
private fun play(range: SVGARange?, reverse: Boolean) {
LogUtils.info(TAG, "================ start animation ================")
val drawable = getSVGADrawable() ?: return
setupDrawable()
mStartFrame = Math.max(0, range?.location ?: 0)
val videoItem = drawable.videoItem
mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0) + (range?.length ?: Int.MAX_VALUE) - 1))
val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame)
animator.interpolator = LinearInterpolator()
animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong()
animator.repeatCount = if (loops <= 0) 99999 else loops - 1
animator.addUpdateListener(mAnimatorUpdateListener)
animator.addListener(mAnimatorListener)
if (reverse) {
animator.reverse()
} else {
animator.start()
}
mAnimator = animator
}
private fun setupDrawable() {
val drawable = getSVGADrawable() ?: return
drawable.cleared = false
drawable.scaleType = scaleType
}
private fun getSVGADrawable(): SVGADrawable? {
return drawable as? SVGADrawable
}
@Suppress("UNNECESSARY_SAFE_CALL")
private fun generateScale(): Double {
var scale = 1.0
try {
val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale
val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale
scale = (getMethod.invoke(animatorClass) as Float).toDouble()
if (scale == 0.0) {
val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale
setMethod.isAccessible = true
setMethod.invoke(animatorClass,1.0f)
scale = 1.0
LogUtils.info(TAG,
"The animation duration scale has been reset to" +
" 1.0x, because you closed it on developer options.")
}
} catch (ignore: Exception) {
ignore.printStackTrace()
}
return scale
}
private fun onAnimatorUpdate(animator: ValueAnimator?) {
val drawable = getSVGADrawable() ?: return
drawable.currentFrame = animator?.animatedValue as Int
val percentage = (drawable.currentFrame + 1).toDouble() / drawable.videoItem.frames.toDouble()
callback?.onStep(drawable.currentFrame, percentage)
}
private fun onAnimationEnd(animation: Animator?) {
isAnimating = false
stopAnimation()
val drawable = getSVGADrawable()
if (drawable != null) {
when (fillMode) {
FillMode.Backward -> {
drawable.currentFrame = mStartFrame
}
FillMode.Forward -> {
drawable.currentFrame = mEndFrame
}
FillMode.Clear -> {
drawable.cleared = true
}
}
}
callback?.onFinished()
}
fun clear() {
getSVGADrawable()?.cleared = true
getSVGADrawable()?.clear()
// 清除对 drawable 的引用
setImageDrawable(null)
}
fun pauseAnimation() {
stopAnimation(false)
callback?.onPause()
}
fun stopAnimation() {
stopAnimation(clear = clearsAfterStop)
}
fun stopAnimation(clear: Boolean) {
mAnimator?.cancel()
mAnimator?.removeAllListeners()
mAnimator?.removeAllUpdateListeners()
getSVGADrawable()?.stop()
getSVGADrawable()?.cleared = clear
}
fun setVideoItem(videoItem: SVGAVideoEntity?) {
setVideoItem(videoItem, SVGADynamicEntity())
}
fun setVideoItem(videoItem: SVGAVideoEntity?, dynamicItem: SVGADynamicEntity?) {
if (videoItem == null) {
setImageDrawable(null)
} else {
val drawable = SVGADrawable(videoItem, dynamicItem ?: SVGADynamicEntity())
drawable.cleared = true
setImageDrawable(drawable)
}
}
fun stepToFrame(frame: Int, andPlay: Boolean) {
pauseAnimation()
val drawable = getSVGADrawable() ?: return
drawable.currentFrame = frame
if (andPlay) {
startAnimation()
mAnimator?.let {
it.currentPlayTime = (Math.max(0.0f, Math.min(1.0f, (frame.toFloat() / drawable.videoItem.frames.toFloat()))) * it.duration).toLong()
}
}
}
fun stepToPercentage(percentage: Double, andPlay: Boolean) {
val drawable = drawable as? SVGADrawable ?: return
var frame = (drawable.videoItem.frames * percentage).toInt()
if (frame >= drawable.videoItem.frames && frame > 0) {
frame = drawable.videoItem.frames - 1
}
stepToFrame(frame, andPlay)
}
fun setOnAnimKeyClickListener(clickListener : SVGAClickAreaListener){
mItemClickAreaListener = clickListener
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action != MotionEvent.ACTION_DOWN) {
return super.onTouchEvent(event)
}
val drawable = getSVGADrawable() ?: return super.onTouchEvent(event)
for ((key, value) in drawable.dynamicItem.mClickMap) {
if (event.x >= value[0] && event.x <= value[2] && event.y >= value[1] && event.y <= value[3]) {
mItemClickAreaListener?.let {
it.onClick(key)
return true
}
}
}
return super.onTouchEvent(event)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
stopAnimation(clearsAfterDetached)
if (clearsAfterDetached) {
clear()
}
}
private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener {
private val weakReference = WeakReference<SVGAImageView>(view)
override fun onAnimationRepeat(animation: Animator?) {
weakReference.get()?.callback?.onRepeat()
}
override fun onAnimationEnd(animation: Animator?) {
weakReference.get()?.onAnimationEnd(animation)
}
override fun onAnimationCancel(animation: Animator?) {
weakReference.get()?.isAnimating = false
}
override fun onAnimationStart(animation: Animator?) {
weakReference.get()?.isAnimating = true
}
} // end of AnimatorListener
private class AnimatorUpdateListener(view: SVGAImageView) : ValueAnimator.AnimatorUpdateListener {
private val weakReference = WeakReference<SVGAImageView>(view)
override fun onAnimationUpdate(animation: ValueAnimator?) {
weakReference.get()?.onAnimatorUpdate(animation)
}
} // end of AnimatorUpdateListener
}

View File

@@ -1,565 +0,0 @@
package com.opensource.svgaplayer
import android.content.Context
import android.net.http.HttpResponseCache
import android.os.Handler
import android.os.Looper
import com.opensource.svgaplayer.proto.MovieEntity
import com.opensource.svgaplayer.utils.log.LogUtils
import org.json.JSONObject
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.atomic.AtomicInteger
import java.util.zip.Inflater
import java.util.zip.ZipInputStream
/**
* Created by PonyCui 16/6/18.
*/
private var fileLock: Int = 0
private var isUnzipping = false
class SVGAParser(context: Context?) {
private var mContext = context?.applicationContext
init {
SVGACache.onCreate(context)
}
@Volatile
private var mFrameWidth: Int = 0
@Volatile
private var mFrameHeight: Int = 0
interface ParseCompletion {
fun onComplete(videoItem: SVGAVideoEntity)
fun onError()
}
interface PlayCallback{
fun onPlay(file: List<File>)
}
open class FileDownloader {
var noCache = false
open fun resume(url: URL, complete: (inputStream: InputStream) -> Unit, failure: (e: Exception) -> Unit): () -> Unit {
var cancelled = false
val cancelBlock = {
cancelled = true
}
threadPoolExecutor.execute {
try {
LogUtils.info(TAG, "================ svga file download start ================")
if (HttpResponseCache.getInstalled() == null && !noCache) {
LogUtils.error(TAG, "SVGAParser can not handle cache before install HttpResponseCache. see https://github.com/yyued/SVGAPlayer-Android#cache")
LogUtils.error(TAG, "在配置 HttpResponseCache 前 SVGAParser 无法缓存. 查看 https://github.com/yyued/SVGAPlayer-Android#cache ")
}
(url.openConnection() as? HttpURLConnection)?.let {
it.connectTimeout = 20 * 1000
it.requestMethod = "GET"
it.setRequestProperty("Connection", "close")
it.connect()
it.inputStream.use { inputStream ->
ByteArrayOutputStream().use { outputStream ->
val buffer = ByteArray(4096)
var count: Int
while (true) {
if (cancelled) {
LogUtils.warn(TAG, "================ svga file download canceled ================")
break
}
count = inputStream.read(buffer, 0, 4096)
if (count == -1) {
break
}
outputStream.write(buffer, 0, count)
}
if (cancelled) {
LogUtils.warn(TAG, "================ svga file download canceled ================")
return@execute
}
ByteArrayInputStream(outputStream.toByteArray()).use {
LogUtils.info(TAG, "================ svga file download complete ================")
complete(it)
}
}
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "================ svga file download fail ================")
LogUtils.error(TAG, "error: ${e.message}")
e.printStackTrace()
failure(e)
}
}
return cancelBlock
}
}
var fileDownloader = FileDownloader()
companion object {
private const val TAG = "SVGAParser"
private val threadNum = AtomicInteger(0)
private var mShareParser = SVGAParser(null)
internal var threadPoolExecutor = Executors.newCachedThreadPool { r ->
Thread(r, "SVGAParser-Thread-${threadNum.getAndIncrement()}")
}
fun setThreadPoolExecutor(executor: ThreadPoolExecutor) {
threadPoolExecutor = executor
}
fun shareParser(): SVGAParser {
return mShareParser
}
}
fun init(context: Context) {
mContext = context.applicationContext
SVGACache.onCreate(mContext)
}
fun setFrameSize(frameWidth: Int, frameHeight: Int) {
mFrameWidth = frameWidth
mFrameHeight = frameHeight
}
fun decodeFromAssets(
name: String,
callback: ParseCompletion?,
playCallback: PlayCallback? = null
) {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
}
LogUtils.info(TAG, "================ decode $name from assets ================")
threadPoolExecutor.execute {
try {
mContext?.assets?.open(name)?.let {
this.decodeFromInputStream(
it,
SVGACache.buildCacheKey("file:///assets/$name"),
callback,
true,
playCallback,
alias = name
)
}
} catch (e: Exception) {
this.invokeErrorCallback(e, callback, name)
}
}
}
fun decodeFromURL(
url: URL,
callback: ParseCompletion?,
playCallback: PlayCallback? = null
): (() -> Unit)? {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return null
}
val urlPath = url.toString()
LogUtils.info(TAG, "================ decode from url: $urlPath ================")
val cacheKey = SVGACache.buildCacheKey(url);
return if (SVGACache.isCached(cacheKey)) {
LogUtils.info(TAG, "this url cached")
threadPoolExecutor.execute {
if (SVGACache.isDefaultCache()) {
this.decodeFromCacheKey(cacheKey, callback, alias = urlPath)
} else {
this.decodeFromSVGAFileCacheKey(cacheKey, callback, playCallback, alias = urlPath)
}
}
return null
} else {
LogUtils.info(TAG, "no cached, prepare to download")
fileDownloader.resume(url, {
this.decodeFromInputStream(
it,
cacheKey,
callback,
false,
playCallback,
alias = urlPath
)
}, {
LogUtils.error(
TAG,
"================ svga file: $url download fail ================"
)
this.invokeErrorCallback(it, callback, alias = urlPath)
})
}
}
/**
* 读取解析本地缓存的 svga 文件.
*/
fun decodeFromSVGAFileCacheKey(
cacheKey: String,
callback: ParseCompletion?,
playCallback: PlayCallback?,
alias: String? = null
) {
threadPoolExecutor.execute {
try {
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity ================")
FileInputStream(SVGACache.buildSvgaFile(cacheKey)).use { inputStream ->
readAsBytes(inputStream)?.let { bytes ->
if (isZipFile(bytes)) {
this.decodeFromCacheKey(cacheKey, callback, alias)
} else {
LogUtils.info(TAG, "inflate start")
inflate(bytes)?.let {
LogUtils.info(TAG, "inflate complete")
val videoItem = SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
File(cacheKey),
mFrameWidth,
mFrameHeight
)
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
videoItem.prepare({
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
this.invokeCompleteCallback(videoItem, callback, alias)
},playCallback)
} ?: this.invokeErrorCallback(
Exception("inflate(bytes) cause exception"),
callback,
alias
)
}
} ?: this.invokeErrorCallback(
Exception("readAsBytes(inputStream) cause exception"),
callback,
alias
)
}
} catch (e: java.lang.Exception) {
this.invokeErrorCallback(e, callback, alias)
} finally {
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity end ================")
}
}
}
fun decodeFromInputStream(
inputStream: InputStream,
cacheKey: String,
callback: ParseCompletion?,
closeInputStream: Boolean = false,
playCallback: PlayCallback? = null,
alias: String? = null
) {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
}
LogUtils.info(TAG, "================ decode $alias from input stream ================")
threadPoolExecutor.execute {
try {
readAsBytes(inputStream)?.let { bytes ->
if (isZipFile(bytes)) {
LogUtils.info(TAG, "decode from zip file")
if (!SVGACache.buildCacheDir(cacheKey).exists() || isUnzipping) {
synchronized(fileLock) {
if (!SVGACache.buildCacheDir(cacheKey).exists()) {
isUnzipping = true
LogUtils.info(TAG, "no cached, prepare to unzip")
ByteArrayInputStream(bytes).use {
unzip(it, cacheKey)
isUnzipping = false
LogUtils.info(TAG, "unzip success")
}
}
}
}
this.decodeFromCacheKey(cacheKey, callback, alias)
} else {
if (!SVGACache.isDefaultCache()) {
// 如果 SVGACache 设置类型为 FILE
threadPoolExecutor.execute {
SVGACache.buildSvgaFile(cacheKey).let { cacheFile ->
try {
cacheFile.takeIf { !it.exists() }?.createNewFile()
FileOutputStream(cacheFile).write(bytes)
} catch (e: Exception) {
LogUtils.error(TAG, "create cache file fail.", e)
cacheFile.delete()
}
}
}
}
LogUtils.info(TAG, "inflate start")
inflate(bytes)?.let {
LogUtils.info(TAG, "inflate complete")
val videoItem = SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
File(cacheKey),
mFrameWidth,
mFrameHeight
)
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
videoItem.prepare({
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
this.invokeCompleteCallback(videoItem, callback, alias)
},playCallback)
} ?: this.invokeErrorCallback(
Exception("inflate(bytes) cause exception"),
callback,
alias
)
}
} ?: this.invokeErrorCallback(
Exception("readAsBytes(inputStream) cause exception"),
callback,
alias
)
} catch (e: java.lang.Exception) {
this.invokeErrorCallback(e, callback, alias)
} finally {
if (closeInputStream) {
inputStream.close()
}
LogUtils.info(TAG, "================ decode $alias from input stream end ================")
}
}
}
/**
* @deprecated from 2.4.0
*/
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromAssets(assetsName, callback)"))
fun parse(assetsName: String, callback: ParseCompletion?) {
this.decodeFromAssets(assetsName, callback,null)
}
/**
* @deprecated from 2.4.0
*/
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromURL(url, callback)"))
fun parse(url: URL, callback: ParseCompletion?) {
this.decodeFromURL(url, callback,null)
}
/**
* @deprecated from 2.4.0
*/
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream)"))
fun parse(
inputStream: InputStream,
cacheKey: String,
callback: ParseCompletion?,
closeInputStream: Boolean = false
) {
this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream,null)
}
private fun invokeCompleteCallback(
videoItem: SVGAVideoEntity,
callback: ParseCompletion?,
alias: String?
) {
Handler(Looper.getMainLooper()).post {
LogUtils.info(TAG, "================ $alias parser complete ================")
callback?.onComplete(videoItem)
}
}
private fun invokeErrorCallback(
e: Exception,
callback: ParseCompletion?,
alias: String?
) {
e.printStackTrace()
LogUtils.error(TAG, "================ $alias parser error ================")
LogUtils.error(TAG, "$alias parse error", e)
Handler(Looper.getMainLooper()).post {
callback?.onError()
}
}
private fun decodeFromCacheKey(
cacheKey: String,
callback: ParseCompletion?,
alias: String?
) {
LogUtils.info(TAG, "================ decode $alias from cache ================")
LogUtils.debug(TAG, "decodeFromCacheKey called with cacheKey : $cacheKey")
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
}
try {
val cacheDir = SVGACache.buildCacheDir(cacheKey)
File(cacheDir, "movie.binary").takeIf { it.isFile }?.let { binaryFile ->
try {
LogUtils.info(TAG, "binary change to entity")
FileInputStream(binaryFile).use {
LogUtils.info(TAG, "binary change to entity success")
this.invokeCompleteCallback(
SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
cacheDir,
mFrameWidth,
mFrameHeight
),
callback,
alias
)
}
} catch (e: Exception) {
LogUtils.error(TAG, "binary change to entity fail", e)
cacheDir.delete()
binaryFile.delete()
throw e
}
}
File(cacheDir, "movie.spec").takeIf { it.isFile }?.let { jsonFile ->
try {
LogUtils.info(TAG, "spec change to entity")
FileInputStream(jsonFile).use { fileInputStream ->
ByteArrayOutputStream().use { byteArrayOutputStream ->
val buffer = ByteArray(2048)
while (true) {
val size = fileInputStream.read(buffer, 0, buffer.size)
if (size == -1) {
break
}
byteArrayOutputStream.write(buffer, 0, size)
}
byteArrayOutputStream.toString().let {
JSONObject(it).let {
LogUtils.info(TAG, "spec change to entity success")
this.invokeCompleteCallback(
SVGAVideoEntity(
it,
cacheDir,
mFrameWidth,
mFrameHeight
),
callback,
alias
)
}
}
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "$alias movie.spec change to entity fail", e)
cacheDir.delete()
jsonFile.delete()
throw e
}
}
} catch (e: Exception) {
this.invokeErrorCallback(e, callback, alias)
}
}
private fun readAsBytes(inputStream: InputStream): ByteArray? {
ByteArrayOutputStream().use { byteArrayOutputStream ->
val byteArray = ByteArray(2048)
while (true) {
val count = inputStream.read(byteArray, 0, 2048)
if (count <= 0) {
break
} else {
byteArrayOutputStream.write(byteArray, 0, count)
}
}
return byteArrayOutputStream.toByteArray()
}
}
private fun inflate(byteArray: ByteArray): ByteArray? {
val inflater = Inflater()
inflater.setInput(byteArray, 0, byteArray.size)
val inflatedBytes = ByteArray(2048)
ByteArrayOutputStream().use { inflatedOutputStream ->
while (true) {
val count = inflater.inflate(inflatedBytes, 0, 2048)
if (count <= 0) {
break
} else {
inflatedOutputStream.write(inflatedBytes, 0, count)
}
}
inflater.end()
return inflatedOutputStream.toByteArray()
}
}
// 是否是 zip 文件
private fun isZipFile(bytes: ByteArray): Boolean {
return bytes.size > 4 && bytes[0].toInt() == 80 && bytes[1].toInt() == 75 && bytes[2].toInt() == 3 && bytes[3].toInt() == 4
}
// 解压
private fun unzip(inputStream: InputStream, cacheKey: String) {
LogUtils.info(TAG, "================ unzip prepare ================")
val cacheDir = SVGACache.buildCacheDir(cacheKey)
cacheDir.mkdirs()
try {
BufferedInputStream(inputStream).use {
ZipInputStream(it).use { zipInputStream ->
while (true) {
val zipItem = zipInputStream.nextEntry ?: break
if (zipItem.name.contains("../")) {
// 解压路径存在路径穿越问题,直接过滤
continue
}
if (zipItem.name.contains("/")) {
continue
}
val file = File(cacheDir, zipItem.name)
ensureUnzipSafety(file, cacheDir.absolutePath)
FileOutputStream(file).use { fileOutputStream ->
val buff = ByteArray(2048)
while (true) {
val readBytes = zipInputStream.read(buff)
if (readBytes <= 0) {
break
}
fileOutputStream.write(buff, 0, readBytes)
}
}
LogUtils.error(TAG, "================ unzip complete ================")
zipInputStream.closeEntry()
}
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "================ unzip error ================")
LogUtils.error(TAG, "error", e)
SVGACache.clearDir(cacheDir.absolutePath)
cacheDir.delete()
throw e
}
}
// 检查 zip 路径穿透
private fun ensureUnzipSafety(outputFile: File, dstDirPath: String) {
val dstDirCanonicalPath = File(dstDirPath).canonicalPath
val outputFileCanonicalPath = outputFile.canonicalPath
if (!outputFileCanonicalPath.startsWith(dstDirCanonicalPath)) {
throw IOException("Found Zip Path Traversal Vulnerability with $dstDirCanonicalPath")
}
}
}

View File

@@ -1,19 +0,0 @@
package com.opensource.svgaplayer
import android.content.Context
import android.util.AttributeSet
/**
* Created by cuiminghui on 2017/3/30.
* @deprecated from 2.4.0
*/
@Deprecated("This class has been deprecated from 2.4.0. We don't recommend you to use it.")
class SVGAPlayer: SVGAImageView {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
}

View File

@@ -1,194 +0,0 @@
package com.opensource.svgaplayer
/**
* @author Devin
*
* Created on 2/24/21.
*/
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.SoundPool
import android.os.Build
import com.opensource.svgaplayer.utils.log.LogUtils
import java.io.FileDescriptor
/**
* Author : llk
* Time : 2020/10/24
* Description : svga 音频加载管理类
* 将 SoundPool 抽取到单例里边,规避 load 资源之后不回调 onLoadComplete 的问题。
*
* 需要对 SVGASoundManager 进行初始化
*
* 相关文章Android SoundPool 崩溃问题研究
* https://zhuanlan.zhihu.com/p/29985198
*/
object SVGASoundManager {
private val TAG = SVGASoundManager::class.java.simpleName
private var soundPool: SoundPool? = null
private val soundCallBackMap: MutableMap<Int, SVGASoundCallBack> = mutableMapOf()
/**
* 音量设置,范围在 [0, 1] 之间
*/
private var volume: Float = 1f
/**
* 音频回调
*/
internal interface SVGASoundCallBack {
// 音量发生变化
fun onVolumeChange(value: Float)
// 音频加载完成
fun onComplete()
}
fun init() {
init(20)
}
fun init(maxStreams: Int) {
LogUtils.debug(TAG, "**************** init **************** $maxStreams")
if (soundPool != null) {
return
}
soundPool = getSoundPool(maxStreams)
soundPool?.setOnLoadCompleteListener { _, soundId, status ->
LogUtils.debug(TAG, "SoundPool onLoadComplete soundId=$soundId status=$status")
if (status == 0) { //加载该声音成功
if (soundCallBackMap.containsKey(soundId)) {
soundCallBackMap[soundId]?.onComplete()
}
}
}
}
fun release() {
LogUtils.debug(TAG, "**************** release ****************")
if (soundCallBackMap.isNotEmpty()) {
soundCallBackMap.clear()
}
}
/**
* 根据当前播放实体,设置音量
*
* @param volume 范围在 [0, 1]
* @param entity 根据需要控制对应 entity 音量大小,若为空则控制所有正在播放的音频音量
*/
fun setVolume(volume: Float, entity: SVGAVideoEntity? = null) {
if (!checkInit()) {
return
}
if (volume < 0f || volume > 1f) {
LogUtils.error(TAG, "The volume level is in the range of 0 to 1 ")
return
}
if (entity == null) {
this.volume = volume
val iterator = soundCallBackMap.entries.iterator()
while (iterator.hasNext()) {
val e = iterator.next()
e.value.onVolumeChange(volume)
}
return
}
val soundPool = soundPool ?: return
entity.audioList.forEach { audio ->
val streamId = audio.playID ?: return
soundPool.setVolume(streamId, volume, volume)
}
}
/**
* 是否初始化
* 如果没有初始化就使用原来SvgaPlayer库的音频加载逻辑。
* @return true 则已初始化, 否则为 false
*/
internal fun isInit(): Boolean {
return soundPool != null
}
private fun checkInit(): Boolean {
val isInit = isInit()
if (!isInit) {
LogUtils.error(TAG, "soundPool is null, you need call init() !!!")
}
return isInit
}
private fun getSoundPool(maxStreams: Int) = if (Build.VERSION.SDK_INT >= 21) {
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
SoundPool.Builder().setAudioAttributes(attributes)
.setMaxStreams(maxStreams)
.build()
} else {
SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0)
}
internal fun load(callBack: SVGASoundCallBack?,
fd: FileDescriptor?,
offset: Long,
length: Long,
priority: Int): Int {
if (!checkInit()) return -1
val soundId = soundPool!!.load(fd, offset, length, priority)
LogUtils.debug(TAG, "load soundId=$soundId callBack=$callBack")
if (callBack != null && !soundCallBackMap.containsKey(soundId)) {
soundCallBackMap[soundId] = callBack
}
return soundId
}
internal fun unload(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "unload soundId=$soundId")
soundPool!!.unload(soundId)
soundCallBackMap.remove(soundId)
}
internal fun play(soundId: Int): Int {
if (!checkInit()) return -1
LogUtils.debug(TAG, "play soundId=$soundId")
return soundPool!!.play(soundId, volume, volume, 1, 0, 1.0f)
}
internal fun stop(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "stop soundId=$soundId")
soundPool!!.stop(soundId)
}
internal fun resume(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "stop soundId=$soundId")
soundPool!!.resume(soundId)
}
internal fun pause(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "pause soundId=$soundId")
soundPool!!.pause(soundId)
}
}

View File

@@ -1,347 +0,0 @@
package com.opensource.svgaplayer
import android.graphics.Bitmap
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.SoundPool
import android.os.Build
import com.opensource.svgaplayer.bitmap.SVGABitmapByteArrayDecoder
import com.opensource.svgaplayer.bitmap.SVGABitmapFileDecoder
import com.opensource.svgaplayer.entities.SVGAAudioEntity
import com.opensource.svgaplayer.entities.SVGAVideoSpriteEntity
import com.opensource.svgaplayer.proto.AudioEntity
import com.opensource.svgaplayer.proto.MovieEntity
import com.opensource.svgaplayer.proto.MovieParams
import com.opensource.svgaplayer.utils.SVGARect
import com.opensource.svgaplayer.utils.log.LogUtils
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.*
import kotlin.collections.ArrayList
/**
* Created by PonyCui on 16/6/18.
*/
class SVGAVideoEntity {
private val TAG = "SVGAVideoEntity"
var antiAlias = true
var movieItem: MovieEntity? = null
var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0)
private set
var FPS = 15
private set
var frames: Int = 0
private set
internal var spriteList: List<SVGAVideoSpriteEntity> = emptyList()
internal var audioList: List<SVGAAudioEntity> = emptyList()
internal var soundPool: SoundPool? = null
private var soundCallback: SVGASoundManager.SVGASoundCallBack? = null
internal var imageMap = HashMap<String, Bitmap>()
private var mCacheDir: File
private var mFrameHeight = 0
private var mFrameWidth = 0
private var mPlayCallback: SVGAParser.PlayCallback?=null
private lateinit var mCallback: () -> Unit
constructor(json: JSONObject, cacheDir: File) : this(json, cacheDir, 0, 0)
constructor(json: JSONObject, cacheDir: File, frameWidth: Int, frameHeight: Int) {
mFrameWidth = frameWidth
mFrameHeight = frameHeight
mCacheDir = cacheDir
val movieJsonObject = json.optJSONObject("movie") ?: return
setupByJson(movieJsonObject)
try {
parserImages(json)
} catch (e: Exception) {
e.printStackTrace()
} catch (e: OutOfMemoryError) {
e.printStackTrace()
}
resetSprites(json)
}
private fun setupByJson(movieObject: JSONObject) {
movieObject.optJSONObject("viewBox")?.let { viewBoxObject ->
val width = viewBoxObject.optDouble("width", 0.0)
val height = viewBoxObject.optDouble("height", 0.0)
videoSize = SVGARect(0.0, 0.0, width, height)
}
FPS = movieObject.optInt("fps", 20)
frames = movieObject.optInt("frames", 0)
}
constructor(entity: MovieEntity, cacheDir: File) : this(entity, cacheDir, 0, 0)
constructor(entity: MovieEntity, cacheDir: File, frameWidth: Int, frameHeight: Int) {
this.mFrameWidth = frameWidth
this.mFrameHeight = frameHeight
this.mCacheDir = cacheDir
this.movieItem = entity
entity.params?.let(this::setupByMovie)
try {
parserImages(entity)
} catch (e: Exception) {
e.printStackTrace()
} catch (e: OutOfMemoryError) {
e.printStackTrace()
}
resetSprites(entity)
}
private fun setupByMovie(movieParams: MovieParams) {
val width = (movieParams.viewBoxWidth ?: 0.0f).toDouble()
val height = (movieParams.viewBoxHeight ?: 0.0f).toDouble()
videoSize = SVGARect(0.0, 0.0, width, height)
FPS = movieParams.fps ?: 20
frames = movieParams.frames ?: 0
}
internal fun prepare(callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) {
mCallback = callback
mPlayCallback = playCallback
if (movieItem == null) {
mCallback()
} else {
setupAudios(movieItem!!) {
mCallback()
}
}
}
private fun parserImages(json: JSONObject) {
val imgJson = json.optJSONObject("images") ?: return
imgJson.keys().forEach { imgKey ->
val filePath = generateBitmapFilePath(imgJson[imgKey].toString(), imgKey)
if (filePath.isEmpty()) {
return
}
val bitmapKey = imgKey.replace(".matte", "")
val bitmap = createBitmap(filePath)
if (bitmap != null) {
imageMap[bitmapKey] = bitmap
}
}
}
private fun generateBitmapFilePath(imgName: String, imgKey: String): String {
val path = mCacheDir.absolutePath + "/" + imgName
val path1 = "$path.png"
val path2 = mCacheDir.absolutePath + "/" + imgKey + ".png"
return when {
File(path).exists() -> path
File(path1).exists() -> path1
File(path2).exists() -> path2
else -> ""
}
}
private fun createBitmap(filePath: String): Bitmap? {
return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight)
}
private fun parserImages(obj: MovieEntity) {
obj.images?.entries?.forEach { entry ->
val byteArray = entry.value.toByteArray()
if (byteArray.count() < 4) {
return@forEach
}
val fileTag = byteArray.slice(IntRange(0, 3))
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
return@forEach
}
val filePath = generateBitmapFilePath(entry.value.utf8(), entry.key)
createBitmap(byteArray, filePath)?.let { bitmap ->
imageMap[entry.key] = bitmap
}
}
}
private fun createBitmap(byteArray: ByteArray, filePath: String): Bitmap? {
val bitmap = SVGABitmapByteArrayDecoder.decodeBitmapFrom(byteArray, mFrameWidth, mFrameHeight)
return bitmap ?: createBitmap(filePath)
}
private fun resetSprites(json: JSONObject) {
val mutableList: MutableList<SVGAVideoSpriteEntity> = mutableListOf()
json.optJSONArray("sprites")?.let { item ->
for (i in 0 until item.length()) {
item.optJSONObject(i)?.let { entryJson ->
mutableList.add(SVGAVideoSpriteEntity(entryJson))
}
}
}
spriteList = mutableList.toList()
}
private fun resetSprites(entity: MovieEntity) {
spriteList = entity.sprites?.map {
return@map SVGAVideoSpriteEntity(it)
} ?: listOf()
}
private fun setupAudios(entity: MovieEntity, completionBlock: () -> Unit) {
if (entity.audios == null || entity.audios.isEmpty()) {
run(completionBlock)
return
}
setupSoundPool(entity, completionBlock)
val audiosFileMap = generateAudioFileMap(entity)
//repair when audioEntity error can not callback
//如果audiosFileMap为空 soundPool?.load 不会走 导致 setOnLoadCompleteListener 不会回调 导致外层prepare不回调卡住
if (audiosFileMap.size == 0) {
run(completionBlock)
return
}
this.audioList = entity.audios.map { audio ->
return@map createSvgaAudioEntity(audio, audiosFileMap)
}
}
private fun createSvgaAudioEntity(audio: AudioEntity, audiosFileMap: HashMap<String, File>): SVGAAudioEntity {
val item = SVGAAudioEntity(audio)
val startTime = (audio.startTime ?: 0).toDouble()
val totalTime = (audio.totalTime ?: 0).toDouble()
if (totalTime.toInt() == 0) {
// 除数不能为 0
return item
}
// 直接回调文件,后续播放都不走
mPlayCallback?.let {
val fileList: MutableList<File> = ArrayList()
audiosFileMap.forEach { entity ->
fileList.add(entity.value)
}
it.onPlay(fileList)
mCallback()
return item
}
audiosFileMap[audio.audioKey]?.let { file ->
FileInputStream(file).use {
val length = it.available().toDouble()
val offset = ((startTime / totalTime) * length).toLong()
if (SVGASoundManager.isInit()) {
item.soundID = SVGASoundManager.load(soundCallback,
it.fd,
offset,
length.toLong(),
1)
} else {
item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1)
}
}
}
return item
}
private fun generateAudioFile(audioCache: File, value: ByteArray): File {
audioCache.createNewFile()
FileOutputStream(audioCache).write(value)
return audioCache
}
private fun generateAudioFileMap(entity: MovieEntity): HashMap<String, File> {
val audiosDataMap = generateAudioMap(entity)
val audiosFileMap = HashMap<String, File>()
if (audiosDataMap.count() > 0) {
audiosDataMap.forEach {
val audioCache = SVGACache.buildAudioFile(it.key)
audiosFileMap[it.key] =
audioCache.takeIf { file -> file.exists() } ?: generateAudioFile(
audioCache,
it.value
)
}
}
return audiosFileMap
}
private fun generateAudioMap(entity: MovieEntity): HashMap<String, ByteArray> {
val audiosDataMap = HashMap<String, ByteArray>()
entity.images?.entries?.forEach {
val imageKey = it.key
val byteArray = it.value.toByteArray()
if (byteArray.count() < 4) {
return@forEach
}
val fileTag = byteArray.slice(IntRange(0, 3))
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
audiosDataMap[imageKey] = byteArray
}else if(fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108){
audiosDataMap[imageKey] = byteArray
}
}
return audiosDataMap
}
private fun setupSoundPool(entity: MovieEntity, completionBlock: () -> Unit) {
var soundLoaded = 0
if (SVGASoundManager.isInit()) {
soundCallback = object : SVGASoundManager.SVGASoundCallBack {
override fun onVolumeChange(value: Float) {
SVGASoundManager.setVolume(value, this@SVGAVideoEntity)
}
override fun onComplete() {
soundLoaded++
if (soundLoaded >= entity.audios.count()) {
completionBlock()
}
}
}
return
}
soundPool = generateSoundPool(entity)
LogUtils.info("SVGAParser", "pool_start")
soundPool?.setOnLoadCompleteListener { _, _, _ ->
LogUtils.info("SVGAParser", "pool_complete")
soundLoaded++
if (soundLoaded >= entity.audios.count()) {
completionBlock()
}
}
}
private fun generateSoundPool(entity: MovieEntity): SoundPool? {
return try {
if (Build.VERSION.SDK_INT >= 21) {
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
SoundPool.Builder().setAudioAttributes(attributes)
.setMaxStreams(12.coerceAtMost(entity.audios.count()))
.build()
} else {
SoundPool(12.coerceAtMost(entity.audios.count()), AudioManager.STREAM_MUSIC, 0)
}
} catch (e: Exception) {
LogUtils.error(TAG, e)
null
}
}
fun clear() {
if (SVGASoundManager.isInit()) {
this.audioList.forEach {
it.soundID?.let { id -> SVGASoundManager.unload(id) }
}
soundCallback = null
}
soundPool?.release()
soundPool = null
audioList = emptyList()
spriteList = emptyList()
imageMap.clear()
}
}

View File

@@ -1,33 +0,0 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.BitmapFactory
/**
*
* Create by im_dsd 2020/7/7 17:59
*/
internal object BitmapSampleSizeCalculator {
fun calculate(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (reqHeight <= 0 || reqWidth <= 0) {
return inSampleSize
}
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
}

View File

@@ -1,16 +0,0 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.Bitmap
import android.graphics.BitmapFactory
/**
* 通过字节码解码 Bitmap
*
* Create by im_dsd 2020/7/7 17:50
*/
internal object SVGABitmapByteArrayDecoder : SVGABitmapDecoder<ByteArray>() {
override fun onDecode(data: ByteArray, ops: BitmapFactory.Options): Bitmap? {
return BitmapFactory.decodeByteArray(data, 0, data.count(), ops)
}
}

View File

@@ -1,35 +0,0 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.Bitmap
import android.graphics.BitmapFactory
/**
* Bitmap 解码器
*
* <T> 需要加载的数据类型
*
* Create by im_dsd 2020/7/7 17:39
*/
internal abstract class SVGABitmapDecoder<T> {
fun decodeBitmapFrom(data: T, reqWidth: Int, reqHeight: Int): Bitmap? {
return BitmapFactory.Options().run {
// 如果期望的宽高是合法的, 则开启检测尺寸模式
inJustDecodeBounds = (reqWidth > 0 && reqHeight > 0)
inPreferredConfig = Bitmap.Config.RGB_565
val bitmap = onDecode(data, this)
if (!inJustDecodeBounds) {
return bitmap
}
// Calculate inSampleSize
inSampleSize = BitmapSampleSizeCalculator.calculate(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
onDecode(data, this)
}
}
abstract fun onDecode(data: T, ops: BitmapFactory.Options): Bitmap?
}

View File

@@ -1,16 +0,0 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.Bitmap
import android.graphics.BitmapFactory
/**
* 通过文件解码 Bitmap
*
* Create by im_dsd 2020/7/7 17:50
*/
internal object SVGABitmapFileDecoder : SVGABitmapDecoder<String>() {
override fun onDecode(data: String, ops: BitmapFactory.Options): Bitmap? {
return BitmapFactory.decodeFile(data, ops)
}
}

View File

@@ -1,53 +0,0 @@
package com.opensource.svgaplayer.drawer
import android.graphics.Canvas
import android.widget.ImageView
import com.opensource.svgaplayer.SVGAVideoEntity
import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity
import com.opensource.svgaplayer.utils.Pools
import com.opensource.svgaplayer.utils.SVGAScaleInfo
import kotlin.math.max
/**
* Created by cuiminghui on 2017/3/29.
*/
open internal class SGVADrawer(val videoItem: SVGAVideoEntity) {
val scaleInfo = SVGAScaleInfo()
private val spritePool = Pools.SimplePool<SVGADrawerSprite>(max(1, videoItem.spriteList.size))
inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) {
val matteKey get() = _matteKey
val imageKey get() = _imageKey
val frameEntity get() = _frameEntity!!
}
internal fun requestFrameSprites(frameIndex: Int): List<SVGADrawerSprite> {
return videoItem.spriteList.mapNotNull {
if (frameIndex >= 0 && frameIndex < it.frames.size) {
it.imageKey?.let { imageKey ->
if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) {
return@mapNotNull null
}
return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply {
_matteKey = it.matteKey
_imageKey = it.imageKey
_frameEntity = it.frames[frameIndex]
}
}
}
return@mapNotNull null
}
}
internal fun releaseFrameSprites(sprites: List<SVGADrawerSprite>) {
sprites.forEach { spritePool.release(it) }
}
open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType)
}
}

View File

@@ -1,559 +0,0 @@
package com.opensource.svgaplayer.drawer
import android.graphics.*
import android.os.Build
import android.text.StaticLayout
import android.text.TextUtils
import android.widget.ImageView
import com.opensource.svgaplayer.SVGADynamicEntity
import com.opensource.svgaplayer.SVGASoundManager
import com.opensource.svgaplayer.SVGAVideoEntity
import com.opensource.svgaplayer.entities.SVGAVideoShapeEntity
/**
* Created by cuiminghui on 2017/3/29.
*/
internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity) : SGVADrawer(videoItem) {
private val sharedValues = ShareValues()
private val drawTextCache: HashMap<String, Bitmap> = hashMapOf()
private val pathCache = PathCache()
private var beginIndexList: Array<Boolean>? = null
private var endIndexList: Array<Boolean>? = null
override fun drawFrame(canvas: Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
super.drawFrame(canvas, frameIndex, scaleType)
playAudio(frameIndex)
this.pathCache.onSizeChanged(canvas)
val sprites = requestFrameSprites(frameIndex)
// Filter null sprites
if (sprites.count() <= 0) return
val matteSprites = mutableMapOf<String, SVGADrawerSprite>()
var saveID = -1
beginIndexList = null
endIndexList = null
// Filter no matte layer
var hasMatteLayer = false
sprites.get(0).imageKey?.let {
if (it.endsWith(".matte")) {
hasMatteLayer = true
}
}
sprites.forEachIndexed { index, svgaDrawerSprite ->
// Save matte sprite
svgaDrawerSprite.imageKey?.let {
/// No matte layer included or VERSION Unsopport matte
if (!hasMatteLayer || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// Normal sprite
drawSprite(svgaDrawerSprite, canvas, frameIndex)
// Continue
return@forEachIndexed
}
/// Cache matte sprite
if (it.endsWith(".matte")) {
matteSprites.put(it, svgaDrawerSprite)
// Continue
return@forEachIndexed
}
}
/// Is matte begin
if (isMatteBegin(index, sprites)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
saveID = canvas.saveLayer(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), null)
} else {
canvas.save()
}
}
/// Normal matte
drawSprite(svgaDrawerSprite, canvas, frameIndex)
/// Is matte end
if (isMatteEnd(index, sprites)) {
matteSprites.get(svgaDrawerSprite.matteKey)?.let {
drawSprite(it, this.sharedValues.shareMatteCanvas(canvas.width, canvas.height), frameIndex)
canvas.drawBitmap(this.sharedValues.sharedMatteBitmap(), 0f, 0f, this.sharedValues.shareMattePaint())
if (saveID != -1) {
canvas.restoreToCount(saveID)
} else {
canvas.restore()
}
// Continue
return@forEachIndexed
}
}
}
releaseFrameSprites(sprites)
}
private fun isMatteBegin(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
if (beginIndexList == null) {
val boolArray = Array(sprites.count()) { false }
sprites.forEachIndexed { index, svgaDrawerSprite ->
svgaDrawerSprite.imageKey?.let {
/// Filter matte sprite
if (it.endsWith(".matte")) {
// Continue
return@forEachIndexed
}
}
svgaDrawerSprite.matteKey?.let {
if (it.length > 0) {
sprites.get(index - 1)?.let { lastSprite ->
if (lastSprite.matteKey.isNullOrEmpty()) {
boolArray[index] = true
} else {
if (lastSprite.matteKey != svgaDrawerSprite.matteKey) {
boolArray[index] = true
}
}
}
}
}
}
beginIndexList = boolArray
}
return beginIndexList?.get(spriteIndex) ?: false
}
private fun isMatteEnd(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
if (endIndexList == null) {
val boolArray = Array(sprites.count()) { false }
sprites.forEachIndexed { index, svgaDrawerSprite ->
svgaDrawerSprite.imageKey?.let {
/// Filter matte sprite
if (it.endsWith(".matte")) {
// Continue
return@forEachIndexed
}
}
svgaDrawerSprite.matteKey?.let {
if (it.length > 0) {
// Last one
if (index == sprites.count() - 1) {
boolArray[index] = true
} else {
sprites.get(index + 1)?.let { nextSprite ->
if (nextSprite.matteKey.isNullOrEmpty()) {
boolArray[index] = true
} else {
if (nextSprite.matteKey != svgaDrawerSprite.matteKey) {
boolArray[index] = true
}
}
}
}
}
}
}
endIndexList = boolArray
}
return endIndexList?.get(spriteIndex) ?: false
}
private fun playAudio(frameIndex: Int) {
this.videoItem.audioList.forEach { audio ->
if (audio.startFrame == frameIndex) {
if (SVGASoundManager.isInit()) {
audio.soundID?.let { soundID ->
audio.playID = SVGASoundManager.play(soundID)
}
} else {
this.videoItem.soundPool?.let { soundPool ->
audio.soundID?.let { soundID ->
audio.playID = soundPool.play(soundID, 1.0f, 1.0f, 1, 0, 1.0f)
}
}
}
}
if (audio.endFrame <= frameIndex) {
audio.playID?.let {
if (SVGASoundManager.isInit()) {
SVGASoundManager.stop(it)
} else {
this.videoItem.soundPool?.stop(it)
}
}
audio.playID = null
}
}
}
private fun shareFrameMatrix(transform: Matrix): Matrix {
val matrix = this.sharedValues.sharedMatrix()
matrix.postScale(scaleInfo.scaleFx, scaleInfo.scaleFy)
matrix.postTranslate(scaleInfo.tranFx, scaleInfo.tranFy)
matrix.preConcat(transform)
return matrix
}
private fun drawSprite(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
drawImage(sprite, canvas)
drawShape(sprite, canvas)
drawDynamic(sprite, canvas, frameIndex)
}
private fun drawImage(sprite: SVGADrawerSprite, canvas: Canvas) {
val imageKey = sprite.imageKey ?: return
val isHidden = dynamicItem.dynamicHidden[imageKey] == true
if (isHidden) {
return
}
val bitmapKey = if (imageKey.endsWith(".matte")) imageKey.substring(0, imageKey.length - 6) else imageKey
val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey])
?: return
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
val paint = this.sharedValues.sharedPaint()
paint.isAntiAlias = videoItem.antiAlias
paint.isFilterBitmap = videoItem.antiAlias
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
if (sprite.frameEntity.maskPath != null) {
val maskPath = sprite.frameEntity.maskPath ?: return
canvas.save()
val path = this.sharedValues.sharedPath()
maskPath.buildPath(path)
path.transform(frameMatrix)
canvas.clipPath(path)
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
if (!drawingBitmap.isRecycled) {
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
}
canvas.restore()
} else {
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
if (!drawingBitmap.isRecycled) {
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
}
}
dynamicItem.dynamicIClickArea.let {
it.get(imageKey)?.let { listener ->
val matrixArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
frameMatrix.getValues(matrixArray)
listener.onResponseArea(imageKey, matrixArray[2].toInt()
, matrixArray[5].toInt()
, (drawingBitmap.width * matrixArray[0] + matrixArray[2]).toInt()
, (drawingBitmap.height * matrixArray[4] + matrixArray[5]).toInt())
}
}
drawTextOnBitmap(canvas, drawingBitmap, sprite, frameMatrix)
}
private fun drawTextOnBitmap(canvas: Canvas, drawingBitmap: Bitmap, sprite: SVGADrawerSprite, frameMatrix: Matrix) {
if (dynamicItem.isTextDirty) {
this.drawTextCache.clear()
dynamicItem.isTextDirty = false
}
val imageKey = sprite.imageKey ?: return
var textBitmap: Bitmap? = null
dynamicItem.dynamicText[imageKey]?.let { drawingText ->
dynamicItem.dynamicTextPaint[imageKey]?.let { drawingTextPaint ->
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
val textCanvas = Canvas(textBitmap)
drawingTextPaint.isAntiAlias = true
val fontMetrics = drawingTextPaint.getFontMetrics();
val top = fontMetrics.top
val bottom = fontMetrics.bottom
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
textCanvas.drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
}
}
dynamicItem.dynamicBoringLayoutText[imageKey]?.let {
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
it.paint.isAntiAlias = true
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val textCanvas = Canvas(textBitmap)
textCanvas.translate(0f, ((drawingBitmap.height - it.height) / 2).toFloat())
it.draw(textCanvas)
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
}
dynamicItem.dynamicStaticLayoutText[imageKey]?.let {
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
it.paint.isAntiAlias = true
var layout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var lineMax = try {
val field = StaticLayout::class.java.getDeclaredField("mMaximumVisibleLineCount")
field.isAccessible = true
field.getInt(it)
} catch (e: Exception) {
Int.MAX_VALUE
}
StaticLayout.Builder
.obtain(it.text, 0, it.text.length, it.paint, drawingBitmap.width)
.setAlignment(it.alignment)
.setMaxLines(lineMax)
.setEllipsize(TextUtils.TruncateAt.END)
.build()
} else {
StaticLayout(it.text, 0, it.text.length, it.paint, drawingBitmap.width, it.alignment, it.spacingMultiplier, it.spacingAdd, false)
}
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val textCanvas = Canvas(textBitmap)
textCanvas.translate(0f, ((drawingBitmap.height - layout.height) / 2).toFloat())
layout.draw(textCanvas)
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
}
textBitmap?.let { textBitmap ->
val paint = this.sharedValues.sharedPaint()
paint.isAntiAlias = videoItem.antiAlias
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
if (sprite.frameEntity.maskPath != null) {
val maskPath = sprite.frameEntity.maskPath ?: return@let
canvas.save()
canvas.concat(frameMatrix)
canvas.clipRect(0, 0, drawingBitmap.width, drawingBitmap.height)
val bitmapShader = BitmapShader(textBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
paint.shader = bitmapShader
val path = this.sharedValues.sharedPath()
maskPath.buildPath(path)
canvas.drawPath(path, paint)
canvas.restore()
} else {
paint.isFilterBitmap = videoItem.antiAlias
canvas.drawBitmap(textBitmap, frameMatrix, paint)
}
}
}
private fun drawShape(sprite: SVGADrawerSprite, canvas: Canvas) {
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
sprite.frameEntity.shapes.forEach { shape ->
shape.buildPath()
shape.shapePath?.let {
val paint = this.sharedValues.sharedPaint()
paint.reset()
paint.isAntiAlias = videoItem.antiAlias
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
val path = this.sharedValues.sharedPath()
path.reset()
path.addPath(this.pathCache.buildPath(shape))
val shapeMatrix = this.sharedValues.sharedMatrix2()
shapeMatrix.reset()
shape.transform?.let {
shapeMatrix.postConcat(it)
}
shapeMatrix.postConcat(frameMatrix)
path.transform(shapeMatrix)
shape.styles?.fill?.let {
if (it != 0x00000000) {
paint.style = Paint.Style.FILL
paint.color = it
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
if (alpha != 255) {
paint.alpha = alpha
}
if (sprite.frameEntity.maskPath !== null) canvas.save()
sprite.frameEntity.maskPath?.let { maskPath ->
val path2 = this.sharedValues.sharedPath2()
maskPath.buildPath(path2)
path2.transform(frameMatrix)
canvas.clipPath(path2)
}
canvas.drawPath(path, paint)
if (sprite.frameEntity.maskPath !== null) canvas.restore()
}
}
shape.styles?.strokeWidth?.let {
if (it > 0) {
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
paint.style = Paint.Style.STROKE
shape.styles?.stroke?.let {
paint.color = it
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
if (alpha != 255) {
paint.alpha = alpha
}
}
val scale = matrixScale(frameMatrix)
shape.styles?.strokeWidth?.let {
paint.strokeWidth = it * scale
}
shape.styles?.lineCap?.let {
when {
it.equals("butt", true) -> paint.strokeCap = Paint.Cap.BUTT
it.equals("round", true) -> paint.strokeCap = Paint.Cap.ROUND
it.equals("square", true) -> paint.strokeCap = Paint.Cap.SQUARE
}
}
shape.styles?.lineJoin?.let {
when {
it.equals("miter", true) -> paint.strokeJoin = Paint.Join.MITER
it.equals("round", true) -> paint.strokeJoin = Paint.Join.ROUND
it.equals("bevel", true) -> paint.strokeJoin = Paint.Join.BEVEL
}
}
shape.styles?.miterLimit?.let {
paint.strokeMiter = it.toFloat() * scale
}
shape.styles?.lineDash?.let {
if (it.size == 3 && (it[0] > 0 || it[1] > 0)) {
paint.pathEffect = DashPathEffect(floatArrayOf(
(if (it[0] < 1.0f) 1.0f else it[0]) * scale,
(if (it[1] < 0.1f) 0.1f else it[1]) * scale
), it[2] * scale)
}
}
if (sprite.frameEntity.maskPath !== null) canvas.save()
sprite.frameEntity.maskPath?.let { maskPath ->
val path2 = this.sharedValues.sharedPath2()
maskPath.buildPath(path2)
path2.transform(frameMatrix)
canvas.clipPath(path2)
}
canvas.drawPath(path, paint)
if (sprite.frameEntity.maskPath !== null) canvas.restore()
}
}
}
}
}
private val matrixScaleTempValues = FloatArray(16)
private fun matrixScale(matrix: Matrix): Float {
matrix.getValues(matrixScaleTempValues)
if (matrixScaleTempValues[0] == 0f) {
return 0f
}
var A = matrixScaleTempValues[0].toDouble()
var B = matrixScaleTempValues[3].toDouble()
var C = matrixScaleTempValues[1].toDouble()
var D = matrixScaleTempValues[4].toDouble()
if (A * D == B * C) return 0f
var scaleX = Math.sqrt(A * A + B * B)
A /= scaleX
B /= scaleX
var skew = A * C + B * D
C -= A * skew
D -= B * skew
var scaleY = Math.sqrt(C * C + D * D)
C /= scaleY
D /= scaleY
skew /= scaleY
if (A * D < B * C) {
scaleX = -scaleX
}
return if (scaleInfo.ratioX) Math.abs(scaleX.toFloat()) else Math.abs(scaleY.toFloat())
}
private fun drawDynamic(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
val imageKey = sprite.imageKey ?: return
dynamicItem.dynamicDrawer[imageKey]?.let {
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
canvas.save()
canvas.concat(frameMatrix)
it.invoke(canvas, frameIndex)
canvas.restore()
}
dynamicItem.dynamicDrawerSized[imageKey]?.let {
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
canvas.save()
canvas.concat(frameMatrix)
it.invoke(canvas, frameIndex, sprite.frameEntity.layout.width.toInt(), sprite.frameEntity.layout.height.toInt())
canvas.restore()
}
}
class ShareValues {
private val sharedPaint = Paint()
private val sharedPath = Path()
private val sharedPath2 = Path()
private val sharedMatrix = Matrix()
private val sharedMatrix2 = Matrix()
private val shareMattePaint = Paint()
private var shareMatteCanvas: Canvas? = null
private var sharedMatteBitmap: Bitmap? = null
fun sharedPaint(): Paint {
sharedPaint.reset()
return sharedPaint
}
fun sharedPath(): Path {
sharedPath.reset()
return sharedPath
}
fun sharedPath2(): Path {
sharedPath2.reset()
return sharedPath2
}
fun sharedMatrix(): Matrix {
sharedMatrix.reset()
return sharedMatrix
}
fun sharedMatrix2(): Matrix {
sharedMatrix2.reset()
return sharedMatrix2
}
fun shareMattePaint(): Paint {
shareMattePaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))
return shareMattePaint
}
fun sharedMatteBitmap(): Bitmap {
return sharedMatteBitmap as Bitmap
}
fun shareMatteCanvas(width: Int, height: Int): Canvas {
if (shareMatteCanvas == null) {
sharedMatteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
// shareMatteCanvas = Canvas(sharedMatteBitmap)
}
// val matteCanvas = shareMatteCanvas as Canvas
// matteCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
// return matteCanvas
return Canvas(sharedMatteBitmap)
}
}
class PathCache {
private var canvasWidth: Int = 0
private var canvasHeight: Int = 0
private val cache = HashMap<SVGAVideoShapeEntity, Path>()
fun onSizeChanged(canvas: Canvas) {
if (this.canvasWidth != canvas.width || this.canvasHeight != canvas.height) {
this.cache.clear()
}
this.canvasWidth = canvas.width
this.canvasHeight = canvas.height
}
fun buildPath(shape: SVGAVideoShapeEntity): Path {
if (!this.cache.containsKey(shape)) {
val path = Path()
path.set(shape.shapePath)
this.cache[shape] = path
}
return this.cache[shape]!!
}
}
}

View File

@@ -1,24 +0,0 @@
package com.opensource.svgaplayer.entities
import com.opensource.svgaplayer.proto.AudioEntity
import java.io.FileInputStream
internal class SVGAAudioEntity {
val audioKey: String?
val startFrame: Int
val endFrame: Int
val startTime: Int
val totalTime: Int
var soundID: Int? = null
var playID: Int? = null
constructor(audioItem: AudioEntity) {
this.audioKey = audioItem.audioKey
this.startFrame = audioItem.startFrame ?: 0
this.endFrame = audioItem.endFrame ?: 0
this.startTime = audioItem.startTime ?: 0
this.totalTime = audioItem.totalTime ?: 0
}
}

View File

@@ -1,100 +0,0 @@
package com.opensource.svgaplayer.entities
import android.graphics.Path
import com.opensource.svgaplayer.utils.SVGAPoint
import java.util.*
private val VALID_METHODS: Set<String> = setOf("M", "L", "H", "V", "C", "S", "Q", "R", "A", "Z", "m", "l", "h", "v", "c", "s", "q", "r", "a", "z")
class SVGAPathEntity(originValue: String) {
private val replacedValue: String = if (originValue.contains(",")) originValue.replace(",", " ") else originValue
private var cachedPath: Path? = null
fun buildPath(toPath: Path) {
cachedPath?.let {
toPath.set(it)
return
}
val cachedPath = Path()
val segments = StringTokenizer(this.replacedValue, "MLHVCSQRAZmlhvcsqraz", true)
var currentMethod = ""
while (segments.hasMoreTokens()) {
val segment = segments.nextToken()
if (segment.isEmpty()) { continue }
if (VALID_METHODS.contains(segment)) {
currentMethod = segment
if (currentMethod == "Z" || currentMethod == "z") { operate(cachedPath, currentMethod, StringTokenizer("", "")) }
}
else {
operate(cachedPath, currentMethod, StringTokenizer(segment, " "))
}
}
this.cachedPath = cachedPath
toPath.set(cachedPath)
}
private fun operate(finalPath: Path, method: String, args: StringTokenizer) {
var x0 = 0.0f
var y0 = 0.0f
var x1 = 0.0f
var y1 = 0.0f
var x2 = 0.0f
var y2 = 0.0f
try {
var index = 0
while (args.hasMoreTokens()) {
val s = args.nextToken()
if (s.isEmpty()) {continue}
if (index == 0) { x0 = s.toFloat() }
if (index == 1) { y0 = s.toFloat() }
if (index == 2) { x1 = s.toFloat() }
if (index == 3) { y1 = s.toFloat() }
if (index == 4) { x2 = s.toFloat() }
if (index == 5) { y2 = s.toFloat() }
index++
}
} catch (e: Exception) {}
var currentPoint = SVGAPoint(0.0f, 0.0f, 0.0f)
if (method == "M") {
finalPath.moveTo(x0, y0)
currentPoint = SVGAPoint(x0, y0, 0.0f)
} else if (method == "m") {
finalPath.rMoveTo(x0, y0)
currentPoint = SVGAPoint(currentPoint.x + x0, currentPoint.y + y0, 0.0f)
}
if (method == "L") {
finalPath.lineTo(x0, y0)
} else if (method == "l") {
finalPath.rLineTo(x0, y0)
}
if (method == "C") {
finalPath.cubicTo(x0, y0, x1, y1, x2, y2)
} else if (method == "c") {
finalPath.rCubicTo(x0, y0, x1, y1, x2, y2)
}
if (method == "Q") {
finalPath.quadTo(x0, y0, x1, y1)
} else if (method == "q") {
finalPath.rQuadTo(x0, y0, x1, y1)
}
if (method == "H") {
finalPath.lineTo(x0, currentPoint.y)
} else if (method == "h") {
finalPath.rLineTo(x0, 0f)
}
if (method == "V") {
finalPath.lineTo(currentPoint.x, x0)
} else if (method == "v") {
finalPath.rLineTo(0f, x0)
}
if (method == "Z") {
finalPath.close()
}
else if (method == "z") {
finalPath.close()
}
}
}

View File

@@ -1,356 +0,0 @@
package com.opensource.svgaplayer.entities
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Path
import android.graphics.RectF
import com.opensource.svgaplayer.proto.ShapeEntity
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
/**
* Created by cuiminghui on 2017/2/22.
*/
val sharedPath = Path()
internal class SVGAVideoShapeEntity {
enum class Type {
shape,
rect,
ellipse,
keep
}
class Styles {
var fill = 0x00000000
internal set
var stroke = 0x00000000
internal set
var strokeWidth = 0.0f
internal set
var lineCap = "butt"
internal set
var lineJoin = "miter"
internal set
var miterLimit = 0
internal set
var lineDash = FloatArray(0)
internal set
}
var type = Type.shape
private set
var args: Map<String, Any>? = null
private set
var styles: Styles? = null
private set
var transform: Matrix? = null
private set
constructor(obj: JSONObject) {
parseType(obj)
parseArgs(obj)
parseStyles(obj)
parseTransform(obj)
}
constructor(obj: ShapeEntity) {
parseType(obj)
parseArgs(obj)
parseStyles(obj)
parseTransform(obj)
}
val isKeep: Boolean
get() = type == Type.keep
var shapePath: Path? = null
private fun parseType(obj: JSONObject) {
obj.optString("type")?.let {
when {
it.equals("shape", ignoreCase = true) -> type = Type.shape
it.equals("rect", ignoreCase = true) -> type = Type.rect
it.equals("ellipse", ignoreCase = true) -> type = Type.ellipse
it.equals("keep", ignoreCase = true) -> type = Type.keep
}
}
}
private fun parseType(obj: ShapeEntity) {
obj.type?.let {
type = when (it) {
ShapeEntity.ShapeType.SHAPE -> Type.shape
ShapeEntity.ShapeType.RECT -> Type.rect
ShapeEntity.ShapeType.ELLIPSE -> Type.ellipse
ShapeEntity.ShapeType.KEEP -> Type.keep
}
}
}
private fun parseArgs(obj: JSONObject) {
val args = HashMap<String, Any>()
obj.optJSONObject("args")?.let { values ->
values.keys().forEach { key ->
values.get(key)?.let {
args.put(key, it)
}
}
this.args = args
}
}
private fun parseArgs(obj: ShapeEntity) {
val args = HashMap<String, Any>()
obj.shape?.let {
it.d?.let { args.put("d", it) }
}
obj.ellipse?.let {
args.put("x", it.x ?: 0.0f)
args.put("y", it.y ?: 0.0f)
args.put("radiusX", it.radiusX ?: 0.0f)
args.put("radiusY", it.radiusY ?: 0.0f)
}
obj.rect?.let {
args.put("x", it.x ?: 0.0f)
args.put("y", it.y ?: 0.0f)
args.put("width", it.width ?: 0.0f)
args.put("height", it.height ?: 0.0f)
args.put("cornerRadius", it.cornerRadius ?: 0.0f)
}
this.args = args
}
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
private fun checkValueRange(obj: JSONArray): Float {
return if (
obj.optDouble(0) <= 1 &&
obj.optDouble(1) <= 1 &&
obj.optDouble(2) <= 1
) {
255f
} else {
1f
}
}
// 检查 alpha 的范围是否是 [0f, 1f],或者是 [0f, 255f]
private fun checkAlphaValueRange(obj: JSONArray): Float {
return if (obj.optDouble(3) <= 1) {
255f
} else {
1f
}
}
private fun parseStyles(obj: JSONObject) {
obj.optJSONObject("styles")?.let {
val styles = Styles()
it.optJSONArray("fill")?.let {
if (it.length() == 4) {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.fill = Color.argb(
(it.optDouble(3) * alphaRangeValue).toInt(),
(it.optDouble(0) * mulValue).toInt(),
(it.optDouble(1) * mulValue).toInt(),
(it.optDouble(2) * mulValue).toInt()
)
}
}
it.optJSONArray("stroke")?.let {
if (it.length() == 4) {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.stroke = Color.argb(
(it.optDouble(3) * alphaRangeValue).toInt(),
(it.optDouble(0) * mulValue).toInt(),
(it.optDouble(1) * mulValue).toInt(),
(it.optDouble(2) * mulValue).toInt()
)
}
}
styles.strokeWidth = it.optDouble("strokeWidth", 0.0).toFloat()
styles.lineCap = it.optString("lineCap", "butt")
styles.lineJoin = it.optString("lineJoin", "miter")
styles.miterLimit = it.optInt("miterLimit", 0)
it.optJSONArray("lineDash")?.let {
styles.lineDash = FloatArray(it.length())
for (i in 0 until it.length()) {
styles.lineDash[i] = it.optDouble(i, 0.0).toFloat()
}
}
this.styles = styles
}
}
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
private fun checkValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
return if (
(color.r ?: 0f) <= 1 &&
(color.g ?: 0f) <= 1 &&
(color.b ?: 0f) <= 1
) {
255f
} else {
1f
}
}
// 检查 alpha 范围是否是 [0f, 1f],有可能是 [0f, 255f]
private fun checkAlphaValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
return if (color.a <= 1f) {
255f
} else {
1f
}
}
private fun parseStyles(obj: ShapeEntity) {
obj.styles?.let {
val styles = Styles()
it.fill?.let {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.fill = Color.argb(
((it.a ?: 0f) * alphaRangeValue).toInt(),
((it.r ?: 0f) * mulValue).toInt(),
((it.g ?: 0f) * mulValue).toInt(),
((it.b ?: 0f) * mulValue).toInt()
)
}
it.stroke?.let {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.stroke = Color.argb(
((it.a ?: 0f) * alphaRangeValue).toInt(),
((it.r ?: 0f) * mulValue).toInt(),
((it.g ?: 0f) * mulValue).toInt(),
((it.b ?: 0f) * mulValue).toInt()
)
}
styles.strokeWidth = it.strokeWidth ?: 0.0f
it.lineCap?.let {
when (it) {
ShapeEntity.ShapeStyle.LineCap.LineCap_BUTT -> styles.lineCap = "butt"
ShapeEntity.ShapeStyle.LineCap.LineCap_ROUND -> styles.lineCap = "round"
ShapeEntity.ShapeStyle.LineCap.LineCap_SQUARE -> styles.lineCap = "square"
}
}
it.lineJoin?.let {
when (it) {
ShapeEntity.ShapeStyle.LineJoin.LineJoin_BEVEL -> styles.lineJoin = "bevel"
ShapeEntity.ShapeStyle.LineJoin.LineJoin_MITER -> styles.lineJoin = "miter"
ShapeEntity.ShapeStyle.LineJoin.LineJoin_ROUND -> styles.lineJoin = "round"
}
}
styles.miterLimit = (it.miterLimit ?: 0.0f).toInt()
styles.lineDash = kotlin.FloatArray(3)
it.lineDashI?.let { styles.lineDash[0] = it }
it.lineDashII?.let { styles.lineDash[1] = it }
it.lineDashIII?.let { styles.lineDash[2] = it }
this.styles = styles
}
}
private fun parseTransform(obj: JSONObject) {
obj.optJSONObject("transform")?.let {
val transform = Matrix()
val arr = FloatArray(9)
val a = it.optDouble("a", 1.0)
val b = it.optDouble("b", 0.0)
val c = it.optDouble("c", 0.0)
val d = it.optDouble("d", 1.0)
val tx = it.optDouble("tx", 0.0)
val ty = it.optDouble("ty", 0.0)
arr[0] = a.toFloat() // a
arr[1] = c.toFloat() // c
arr[2] = tx.toFloat() // tx
arr[3] = b.toFloat() // b
arr[4] = d.toFloat() // d
arr[5] = ty.toFloat() // ty
arr[6] = 0.0.toFloat()
arr[7] = 0.0.toFloat()
arr[8] = 1.0.toFloat()
transform.setValues(arr)
this.transform = transform
}
}
private fun parseTransform(obj: ShapeEntity) {
obj.transform?.let {
val transform = Matrix()
val arr = FloatArray(9)
val a = it.a ?: 1.0f
val b = it.b ?: 0.0f
val c = it.c ?: 0.0f
val d = it.d ?: 1.0f
val tx = it.tx ?: 0.0f
val ty = it.ty ?: 0.0f
arr[0] = a
arr[1] = c
arr[2] = tx
arr[3] = b
arr[4] = d
arr[5] = ty
arr[6] = 0.0f
arr[7] = 0.0f
arr[8] = 1.0f
transform.setValues(arr)
this.transform = transform
}
}
fun buildPath() {
if (this.shapePath != null) {
return
}
sharedPath.reset()
if (this.type == Type.shape) {
(this.args?.get("d") as? String)?.let {
SVGAPathEntity(it).buildPath(sharedPath)
}
} else if (this.type == Type.ellipse) {
val xv = this.args?.get("x") as? Number ?: return
val yv = this.args?.get("y") as? Number ?: return
val rxv = this.args?.get("radiusX") as? Number ?: return
val ryv = this.args?.get("radiusY") as? Number ?: return
val x = xv.toFloat()
val y = yv.toFloat()
val rx = rxv.toFloat()
val ry = ryv.toFloat()
sharedPath.addOval(RectF(x - rx, y - ry, x + rx, y + ry), Path.Direction.CW)
} else if (this.type == Type.rect) {
val xv = this.args?.get("x") as? Number ?: return
val yv = this.args?.get("y") as? Number ?: return
val wv = this.args?.get("width") as? Number ?: return
val hv = this.args?.get("height") as? Number ?: return
val crv = this.args?.get("cornerRadius") as? Number ?: return
val x = xv.toFloat()
val y = yv.toFloat()
val width = wv.toFloat()
val height = hv.toFloat()
val cornerRadius = crv.toFloat()
sharedPath.addRoundRect(RectF(x, y, x + width, y + height), cornerRadius, cornerRadius, Path.Direction.CW)
}
this.shapePath = Path()
this.shapePath?.set(sharedPath)
}
}

View File

@@ -1,60 +0,0 @@
package com.opensource.svgaplayer.entities
import com.opensource.svgaplayer.proto.SpriteEntity
import org.json.JSONObject
/**
* Created by cuiminghui on 2016/10/17.
*/
internal class SVGAVideoSpriteEntity {
val imageKey: String?
val matteKey: String?
val frames: List<SVGAVideoSpriteFrameEntity>
constructor(obj: JSONObject) {
this.imageKey = obj.optString("imageKey")
this.matteKey = obj.optString("matteKey")
val mutableFrames: MutableList<SVGAVideoSpriteFrameEntity> = mutableListOf()
obj.optJSONArray("frames")?.let {
for (i in 0 until it.length()) {
it.optJSONObject(i)?.let {
val frameItem = SVGAVideoSpriteFrameEntity(it)
if (frameItem.shapes.isNotEmpty()) {
frameItem.shapes.first().let {
if (it.isKeep && mutableFrames.size > 0) {
frameItem.shapes = mutableFrames.last().shapes
}
}
}
mutableFrames.add(frameItem)
}
}
}
frames = mutableFrames.toList()
}
constructor(obj: SpriteEntity) {
this.imageKey = obj.imageKey
this.matteKey = obj.matteKey
var lastFrame: SVGAVideoSpriteFrameEntity? = null
frames = obj.frames?.map {
val frameItem = SVGAVideoSpriteFrameEntity(it)
if (frameItem.shapes.isNotEmpty()) {
frameItem.shapes.first().let {
if (it.isKeep) {
lastFrame?.let {
frameItem.shapes = it.shapes
}
}
}
}
lastFrame = frameItem
return@map frameItem
} ?: listOf()
}
}

View File

@@ -1,94 +0,0 @@
package com.opensource.svgaplayer.entities
import android.graphics.Matrix
import com.opensource.svgaplayer.proto.FrameEntity
import com.opensource.svgaplayer.utils.SVGARect
import org.json.JSONObject
/**
* Created by cuiminghui on 2016/10/17.
*/
internal class SVGAVideoSpriteFrameEntity {
var alpha: Double
var layout = SVGARect(0.0, 0.0, 0.0, 0.0)
var transform = Matrix()
var maskPath: SVGAPathEntity? = null
var shapes: List<SVGAVideoShapeEntity> = listOf()
constructor(obj: JSONObject) {
this.alpha = obj.optDouble("alpha", 0.0)
obj.optJSONObject("layout")?.let {
layout = SVGARect(it.optDouble("x", 0.0), it.optDouble("y", 0.0), it.optDouble("width", 0.0), it.optDouble("height", 0.0))
}
obj.optJSONObject("transform")?.let {
val arr = FloatArray(9)
val a = it.optDouble("a", 1.0)
val b = it.optDouble("b", 0.0)
val c = it.optDouble("c", 0.0)
val d = it.optDouble("d", 1.0)
val tx = it.optDouble("tx", 0.0)
val ty = it.optDouble("ty", 0.0)
arr[0] = a.toFloat()
arr[1] = c.toFloat()
arr[2] = tx.toFloat()
arr[3] = b.toFloat()
arr[4] = d.toFloat()
arr[5] = ty.toFloat()
arr[6] = 0.0.toFloat()
arr[7] = 0.0.toFloat()
arr[8] = 1.0.toFloat()
transform.setValues(arr)
}
obj.optString("clipPath")?.let { d ->
if (d.isNotEmpty()) {
maskPath = SVGAPathEntity(d)
}
}
obj.optJSONArray("shapes")?.let {
val mutableList: MutableList<SVGAVideoShapeEntity> = mutableListOf()
for (i in 0 until it.length()) {
it.optJSONObject(i)?.let {
mutableList.add(SVGAVideoShapeEntity(it))
}
}
shapes = mutableList.toList()
}
}
constructor(obj: FrameEntity) {
this.alpha = (obj.alpha ?: 0.0f).toDouble()
obj.layout?.let {
this.layout = SVGARect((it.x ?: 0.0f).toDouble(), (it.y
?: 0.0f).toDouble(), (it.width ?: 0.0f).toDouble(), (it.height
?: 0.0f).toDouble())
}
obj.transform?.let {
val arr = FloatArray(9)
val a = it.a ?: 1.0f
val b = it.b ?: 0.0f
val c = it.c ?: 0.0f
val d = it.d ?: 1.0f
val tx = it.tx ?: 0.0f
val ty = it.ty ?: 0.0f
arr[0] = a
arr[1] = c
arr[2] = tx
arr[3] = b
arr[4] = d
arr[5] = ty
arr[6] = 0.0f
arr[7] = 0.0f
arr[8] = 1.0f
transform.setValues(arr)
}
obj.clipPath?.takeIf { it.isNotEmpty() }?.let {
maskPath = SVGAPathEntity(it)
}
this.shapes = obj.shapes.map {
return@map SVGAVideoShapeEntity(it)
}
}
}

View File

@@ -1,258 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 19:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class AudioEntity extends Message<AudioEntity, AudioEntity.Builder> {
public static final ProtoAdapter<AudioEntity> ADAPTER = new ProtoAdapter_AudioEntity();
private static final long serialVersionUID = 0L;
public static final String DEFAULT_AUDIOKEY = "";
public static final Integer DEFAULT_STARTFRAME = 0;
public static final Integer DEFAULT_ENDFRAME = 0;
public static final Integer DEFAULT_STARTTIME = 0;
public static final Integer DEFAULT_TOTALTIME = 0;
/**
* 音频文件名
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String audioKey;
/**
* 音频播放起始帧
*/
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer startFrame;
/**
* 音频播放结束帧
*/
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer endFrame;
/**
* 音频播放起始时间(相对音频长度)
*/
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer startTime;
/**
* 音频总长度
*/
@WireField(
tag = 5,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer totalTime;
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime) {
this(audioKey, startFrame, endFrame, startTime, totalTime, ByteString.EMPTY);
}
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.audioKey = audioKey;
this.startFrame = startFrame;
this.endFrame = endFrame;
this.startTime = startTime;
this.totalTime = totalTime;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.audioKey = audioKey;
builder.startFrame = startFrame;
builder.endFrame = endFrame;
builder.startTime = startTime;
builder.totalTime = totalTime;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof AudioEntity)) return false;
AudioEntity o = (AudioEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(audioKey, o.audioKey)
&& Internal.equals(startFrame, o.startFrame)
&& Internal.equals(endFrame, o.endFrame)
&& Internal.equals(startTime, o.startTime)
&& Internal.equals(totalTime, o.totalTime);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (audioKey != null ? audioKey.hashCode() : 0);
result = result * 37 + (startFrame != null ? startFrame.hashCode() : 0);
result = result * 37 + (endFrame != null ? endFrame.hashCode() : 0);
result = result * 37 + (startTime != null ? startTime.hashCode() : 0);
result = result * 37 + (totalTime != null ? totalTime.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (audioKey != null) builder.append(", audioKey=").append(audioKey);
if (startFrame != null) builder.append(", startFrame=").append(startFrame);
if (endFrame != null) builder.append(", endFrame=").append(endFrame);
if (startTime != null) builder.append(", startTime=").append(startTime);
if (totalTime != null) builder.append(", totalTime=").append(totalTime);
return builder.replace(0, 2, "AudioEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<AudioEntity, Builder> {
public String audioKey;
public Integer startFrame;
public Integer endFrame;
public Integer startTime;
public Integer totalTime;
public Builder() {
}
/**
* 音频文件名
*/
public Builder audioKey(String audioKey) {
this.audioKey = audioKey;
return this;
}
/**
* 音频播放起始帧
*/
public Builder startFrame(Integer startFrame) {
this.startFrame = startFrame;
return this;
}
/**
* 音频播放结束帧
*/
public Builder endFrame(Integer endFrame) {
this.endFrame = endFrame;
return this;
}
/**
* 音频播放起始时间(相对音频长度)
*/
public Builder startTime(Integer startTime) {
this.startTime = startTime;
return this;
}
/**
* 音频总长度
*/
public Builder totalTime(Integer totalTime) {
this.totalTime = totalTime;
return this;
}
@Override
public AudioEntity build() {
return new AudioEntity(audioKey, startFrame, endFrame, startTime, totalTime, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_AudioEntity extends ProtoAdapter<AudioEntity> {
ProtoAdapter_AudioEntity() {
super(FieldEncoding.LENGTH_DELIMITED, AudioEntity.class);
}
@Override
public int encodedSize(AudioEntity value) {
return (value.audioKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.audioKey) : 0)
+ (value.startFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(2, value.startFrame) : 0)
+ (value.endFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.endFrame) : 0)
+ (value.startTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.startTime) : 0)
+ (value.totalTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(5, value.totalTime) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, AudioEntity value) throws IOException {
if (value.audioKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.audioKey);
if (value.startFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 2, value.startFrame);
if (value.endFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.endFrame);
if (value.startTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.startTime);
if (value.totalTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 5, value.totalTime);
writer.writeBytes(value.unknownFields());
}
@Override
public AudioEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.audioKey(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.startFrame(ProtoAdapter.INT32.decode(reader)); break;
case 3: builder.endFrame(ProtoAdapter.INT32.decode(reader)); break;
case 4: builder.startTime(ProtoAdapter.INT32.decode(reader)); break;
case 5: builder.totalTime(ProtoAdapter.INT32.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public AudioEntity redact(AudioEntity value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,259 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 115:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import okio.ByteString;
public final class FrameEntity extends Message<FrameEntity, FrameEntity.Builder> {
public static final ProtoAdapter<FrameEntity> ADAPTER = new ProtoAdapter_FrameEntity();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_ALPHA = 0.0f;
public static final String DEFAULT_CLIPPATH = "";
/**
* 透明度
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float alpha;
/**
* 初始约束大小
*/
@WireField(
tag = 2,
adapter = "com.opensource.svgaplayer.proto.Layout#ADAPTER"
)
public final Layout layout;
/**
* 2D 变换矩阵
*/
@WireField(
tag = 3,
adapter = "com.opensource.svgaplayer.proto.Transform#ADAPTER"
)
public final Transform transform;
/**
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
*/
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String clipPath;
/**
* 矢量元素列表
*/
@WireField(
tag = 5,
adapter = "com.opensource.svgaplayer.proto.ShapeEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<ShapeEntity> shapes;
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes) {
this(alpha, layout, transform, clipPath, shapes, ByteString.EMPTY);
}
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.alpha = alpha;
this.layout = layout;
this.transform = transform;
this.clipPath = clipPath;
this.shapes = Internal.immutableCopyOf("shapes", shapes);
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.alpha = alpha;
builder.layout = layout;
builder.transform = transform;
builder.clipPath = clipPath;
builder.shapes = Internal.copyOf("shapes", shapes);
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof FrameEntity)) return false;
FrameEntity o = (FrameEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(alpha, o.alpha)
&& Internal.equals(layout, o.layout)
&& Internal.equals(transform, o.transform)
&& Internal.equals(clipPath, o.clipPath)
&& shapes.equals(o.shapes);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (alpha != null ? alpha.hashCode() : 0);
result = result * 37 + (layout != null ? layout.hashCode() : 0);
result = result * 37 + (transform != null ? transform.hashCode() : 0);
result = result * 37 + (clipPath != null ? clipPath.hashCode() : 0);
result = result * 37 + shapes.hashCode();
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (alpha != null) builder.append(", alpha=").append(alpha);
if (layout != null) builder.append(", layout=").append(layout);
if (transform != null) builder.append(", transform=").append(transform);
if (clipPath != null) builder.append(", clipPath=").append(clipPath);
if (!shapes.isEmpty()) builder.append(", shapes=").append(shapes);
return builder.replace(0, 2, "FrameEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<FrameEntity, Builder> {
public Float alpha;
public Layout layout;
public Transform transform;
public String clipPath;
public List<ShapeEntity> shapes;
public Builder() {
shapes = Internal.newMutableList();
}
/**
* 透明度
*/
public Builder alpha(Float alpha) {
this.alpha = alpha;
return this;
}
/**
* 初始约束大小
*/
public Builder layout(Layout layout) {
this.layout = layout;
return this;
}
/**
* 2D 变换矩阵
*/
public Builder transform(Transform transform) {
this.transform = transform;
return this;
}
/**
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
*/
public Builder clipPath(String clipPath) {
this.clipPath = clipPath;
return this;
}
/**
* 矢量元素列表
*/
public Builder shapes(List<ShapeEntity> shapes) {
Internal.checkElementsNotNull(shapes);
this.shapes = shapes;
return this;
}
@Override
public FrameEntity build() {
return new FrameEntity(alpha, layout, transform, clipPath, shapes, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_FrameEntity extends ProtoAdapter<FrameEntity> {
ProtoAdapter_FrameEntity() {
super(FieldEncoding.LENGTH_DELIMITED, FrameEntity.class);
}
@Override
public int encodedSize(FrameEntity value) {
return (value.alpha != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.alpha) : 0)
+ (value.layout != null ? Layout.ADAPTER.encodedSizeWithTag(2, value.layout) : 0)
+ (value.transform != null ? Transform.ADAPTER.encodedSizeWithTag(3, value.transform) : 0)
+ (value.clipPath != null ? ProtoAdapter.STRING.encodedSizeWithTag(4, value.clipPath) : 0)
+ ShapeEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.shapes)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, FrameEntity value) throws IOException {
if (value.alpha != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.alpha);
if (value.layout != null) Layout.ADAPTER.encodeWithTag(writer, 2, value.layout);
if (value.transform != null) Transform.ADAPTER.encodeWithTag(writer, 3, value.transform);
if (value.clipPath != null) ProtoAdapter.STRING.encodeWithTag(writer, 4, value.clipPath);
ShapeEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.shapes);
writer.writeBytes(value.unknownFields());
}
@Override
public FrameEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.alpha(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.layout(Layout.ADAPTER.decode(reader)); break;
case 3: builder.transform(Transform.ADAPTER.decode(reader)); break;
case 4: builder.clipPath(ProtoAdapter.STRING.decode(reader)); break;
case 5: builder.shapes.add(ShapeEntity.ADAPTER.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public FrameEntity redact(FrameEntity value) {
Builder builder = value.newBuilder();
if (builder.layout != null) builder.layout = Layout.ADAPTER.redact(builder.layout);
if (builder.transform != null) builder.transform = Transform.ADAPTER.redact(builder.transform);
Internal.redactElements(builder.shapes, ShapeEntity.ADAPTER);
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,205 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 27:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class Layout extends Message<Layout, Layout.Builder> {
public static final ProtoAdapter<Layout> ADAPTER = new ProtoAdapter_Layout();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_X = 0.0f;
public static final Float DEFAULT_Y = 0.0f;
public static final Float DEFAULT_WIDTH = 0.0f;
public static final Float DEFAULT_HEIGHT = 0.0f;
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float x;
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float y;
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float width;
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float height;
public Layout(Float x, Float y, Float width, Float height) {
this(x, y, width, height, ByteString.EMPTY);
}
public Layout(Float x, Float y, Float width, Float height, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.x = x;
builder.y = y;
builder.width = width;
builder.height = height;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Layout)) return false;
Layout o = (Layout) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(x, o.x)
&& Internal.equals(y, o.y)
&& Internal.equals(width, o.width)
&& Internal.equals(height, o.height);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (x != null ? x.hashCode() : 0);
result = result * 37 + (y != null ? y.hashCode() : 0);
result = result * 37 + (width != null ? width.hashCode() : 0);
result = result * 37 + (height != null ? height.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (x != null) builder.append(", x=").append(x);
if (y != null) builder.append(", y=").append(y);
if (width != null) builder.append(", width=").append(width);
if (height != null) builder.append(", height=").append(height);
return builder.replace(0, 2, "Layout{").append('}').toString();
}
public static final class Builder extends Message.Builder<Layout, Builder> {
public Float x;
public Float y;
public Float width;
public Float height;
public Builder() {
}
public Builder x(Float x) {
this.x = x;
return this;
}
public Builder y(Float y) {
this.y = y;
return this;
}
public Builder width(Float width) {
this.width = width;
return this;
}
public Builder height(Float height) {
this.height = height;
return this;
}
@Override
public Layout build() {
return new Layout(x, y, width, height, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_Layout extends ProtoAdapter<Layout> {
ProtoAdapter_Layout() {
super(FieldEncoding.LENGTH_DELIMITED, Layout.class);
}
@Override
public int encodedSize(Layout value) {
return (value.x != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.x) : 0)
+ (value.y != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.y) : 0)
+ (value.width != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.width) : 0)
+ (value.height != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.height) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, Layout value) throws IOException {
if (value.x != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.x);
if (value.y != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.y);
if (value.width != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.width);
if (value.height != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.height);
writer.writeBytes(value.unknownFields());
}
@Override
public Layout decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.x(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.y(ProtoAdapter.FLOAT.decode(reader)); break;
case 3: builder.width(ProtoAdapter.FLOAT.decode(reader)); break;
case 4: builder.height(ProtoAdapter.FLOAT.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public Layout redact(Layout value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,265 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 123:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import java.util.Map;
import okio.ByteString;
public final class MovieEntity extends Message<MovieEntity, MovieEntity.Builder> {
public static final ProtoAdapter<MovieEntity> ADAPTER = new ProtoAdapter_MovieEntity();
private static final long serialVersionUID = 0L;
public static final String DEFAULT_VERSION = "";
/**
* SVGA 格式版本号
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String version;
/**
* 动画参数
*/
@WireField(
tag = 2,
adapter = "com.opensource.svgaplayer.proto.MovieParams#ADAPTER"
)
public final MovieParams params;
/**
* Key 是位图键名Value 是位图文件名或二进制 PNG 数据。
*/
@WireField(
tag = 3,
keyAdapter = "com.squareup.wire.ProtoAdapter#STRING",
adapter = "com.squareup.wire.ProtoAdapter#BYTES"
)
public final Map<String, ByteString> images;
/**
* 元素列表
*/
@WireField(
tag = 4,
adapter = "com.opensource.svgaplayer.proto.SpriteEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<SpriteEntity> sprites;
/**
* 音频列表
*/
@WireField(
tag = 5,
adapter = "com.opensource.svgaplayer.proto.AudioEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<AudioEntity> audios;
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios) {
this(version, params, images, sprites, audios, ByteString.EMPTY);
}
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.version = version;
this.params = params;
this.images = Internal.immutableCopyOf("images", images);
this.sprites = Internal.immutableCopyOf("sprites", sprites);
this.audios = Internal.immutableCopyOf("audios", audios);
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.version = version;
builder.params = params;
builder.images = Internal.copyOf("images", images);
builder.sprites = Internal.copyOf("sprites", sprites);
builder.audios = Internal.copyOf("audios", audios);
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof MovieEntity)) return false;
MovieEntity o = (MovieEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(version, o.version)
&& Internal.equals(params, o.params)
&& images.equals(o.images)
&& sprites.equals(o.sprites)
&& audios.equals(o.audios);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (version != null ? version.hashCode() : 0);
result = result * 37 + (params != null ? params.hashCode() : 0);
result = result * 37 + images.hashCode();
result = result * 37 + sprites.hashCode();
result = result * 37 + audios.hashCode();
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (version != null) builder.append(", version=").append(version);
if (params != null) builder.append(", params=").append(params);
if (!images.isEmpty()) builder.append(", images=").append(images);
if (!sprites.isEmpty()) builder.append(", sprites=").append(sprites);
if (!audios.isEmpty()) builder.append(", audios=").append(audios);
return builder.replace(0, 2, "MovieEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<MovieEntity, Builder> {
public String version;
public MovieParams params;
public Map<String, ByteString> images;
public List<SpriteEntity> sprites;
public List<AudioEntity> audios;
public Builder() {
images = Internal.newMutableMap();
sprites = Internal.newMutableList();
audios = Internal.newMutableList();
}
/**
* SVGA 格式版本号
*/
public Builder version(String version) {
this.version = version;
return this;
}
/**
* 动画参数
*/
public Builder params(MovieParams params) {
this.params = params;
return this;
}
/**
* Key 是位图键名Value 是位图文件名或二进制 PNG 数据。
*/
public Builder images(Map<String, ByteString> images) {
Internal.checkElementsNotNull(images);
this.images = images;
return this;
}
/**
* 元素列表
*/
public Builder sprites(List<SpriteEntity> sprites) {
Internal.checkElementsNotNull(sprites);
this.sprites = sprites;
return this;
}
/**
* 音频列表
*/
public Builder audios(List<AudioEntity> audios) {
Internal.checkElementsNotNull(audios);
this.audios = audios;
return this;
}
@Override
public MovieEntity build() {
return new MovieEntity(version, params, images, sprites, audios, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_MovieEntity extends ProtoAdapter<MovieEntity> {
private final ProtoAdapter<Map<String, ByteString>> images = ProtoAdapter.newMapAdapter(ProtoAdapter.STRING, ProtoAdapter.BYTES);
ProtoAdapter_MovieEntity() {
super(FieldEncoding.LENGTH_DELIMITED, MovieEntity.class);
}
@Override
public int encodedSize(MovieEntity value) {
return (value.version != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.version) : 0)
+ (value.params != null ? MovieParams.ADAPTER.encodedSizeWithTag(2, value.params) : 0)
+ images.encodedSizeWithTag(3, value.images)
+ SpriteEntity.ADAPTER.asRepeated().encodedSizeWithTag(4, value.sprites)
+ AudioEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.audios)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, MovieEntity value) throws IOException {
if (value.version != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.version);
if (value.params != null) MovieParams.ADAPTER.encodeWithTag(writer, 2, value.params);
images.encodeWithTag(writer, 3, value.images);
SpriteEntity.ADAPTER.asRepeated().encodeWithTag(writer, 4, value.sprites);
AudioEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.audios);
writer.writeBytes(value.unknownFields());
}
@Override
public MovieEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.version(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.params(MovieParams.ADAPTER.decode(reader)); break;
case 3: builder.images.putAll(images.decode(reader)); break;
case 4: builder.sprites.add(SpriteEntity.ADAPTER.decode(reader)); break;
case 5: builder.audios.add(AudioEntity.ADAPTER.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public MovieEntity redact(MovieEntity value) {
Builder builder = value.newBuilder();
if (builder.params != null) builder.params = MovieParams.ADAPTER.redact(builder.params);
Internal.redactElements(builder.sprites, SpriteEntity.ADAPTER);
Internal.redactElements(builder.audios, AudioEntity.ADAPTER);
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,230 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 6:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class MovieParams extends Message<MovieParams, MovieParams.Builder> {
public static final ProtoAdapter<MovieParams> ADAPTER = new ProtoAdapter_MovieParams();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_VIEWBOXWIDTH = 0.0f;
public static final Float DEFAULT_VIEWBOXHEIGHT = 0.0f;
public static final Integer DEFAULT_FPS = 0;
public static final Integer DEFAULT_FRAMES = 0;
/**
* 画布宽
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float viewBoxWidth;
/**
* 画布高
*/
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float viewBoxHeight;
/**
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
*/
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer fps;
/**
* 动画总帧数
*/
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer frames;
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames) {
this(viewBoxWidth, viewBoxHeight, fps, frames, ByteString.EMPTY);
}
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.viewBoxWidth = viewBoxWidth;
this.viewBoxHeight = viewBoxHeight;
this.fps = fps;
this.frames = frames;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.viewBoxWidth = viewBoxWidth;
builder.viewBoxHeight = viewBoxHeight;
builder.fps = fps;
builder.frames = frames;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof MovieParams)) return false;
MovieParams o = (MovieParams) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(viewBoxWidth, o.viewBoxWidth)
&& Internal.equals(viewBoxHeight, o.viewBoxHeight)
&& Internal.equals(fps, o.fps)
&& Internal.equals(frames, o.frames);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (viewBoxWidth != null ? viewBoxWidth.hashCode() : 0);
result = result * 37 + (viewBoxHeight != null ? viewBoxHeight.hashCode() : 0);
result = result * 37 + (fps != null ? fps.hashCode() : 0);
result = result * 37 + (frames != null ? frames.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (viewBoxWidth != null) builder.append(", viewBoxWidth=").append(viewBoxWidth);
if (viewBoxHeight != null) builder.append(", viewBoxHeight=").append(viewBoxHeight);
if (fps != null) builder.append(", fps=").append(fps);
if (frames != null) builder.append(", frames=").append(frames);
return builder.replace(0, 2, "MovieParams{").append('}').toString();
}
public static final class Builder extends Message.Builder<MovieParams, Builder> {
public Float viewBoxWidth;
public Float viewBoxHeight;
public Integer fps;
public Integer frames;
public Builder() {
}
/**
* 画布宽
*/
public Builder viewBoxWidth(Float viewBoxWidth) {
this.viewBoxWidth = viewBoxWidth;
return this;
}
/**
* 画布高
*/
public Builder viewBoxHeight(Float viewBoxHeight) {
this.viewBoxHeight = viewBoxHeight;
return this;
}
/**
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
*/
public Builder fps(Integer fps) {
this.fps = fps;
return this;
}
/**
* 动画总帧数
*/
public Builder frames(Integer frames) {
this.frames = frames;
return this;
}
@Override
public MovieParams build() {
return new MovieParams(viewBoxWidth, viewBoxHeight, fps, frames, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_MovieParams extends ProtoAdapter<MovieParams> {
ProtoAdapter_MovieParams() {
super(FieldEncoding.LENGTH_DELIMITED, MovieParams.class);
}
@Override
public int encodedSize(MovieParams value) {
return (value.viewBoxWidth != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.viewBoxWidth) : 0)
+ (value.viewBoxHeight != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.viewBoxHeight) : 0)
+ (value.fps != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.fps) : 0)
+ (value.frames != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.frames) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, MovieParams value) throws IOException {
if (value.viewBoxWidth != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.viewBoxWidth);
if (value.viewBoxHeight != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.viewBoxHeight);
if (value.fps != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.fps);
if (value.frames != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.frames);
writer.writeBytes(value.unknownFields());
}
@Override
public MovieParams decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.viewBoxWidth(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.viewBoxHeight(ProtoAdapter.FLOAT.decode(reader)); break;
case 3: builder.fps(ProtoAdapter.INT32.decode(reader)); break;
case 4: builder.frames(ProtoAdapter.INT32.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public MovieParams redact(MovieParams value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,202 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 13:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import okio.ByteString;
public final class SpriteEntity extends Message<SpriteEntity, SpriteEntity.Builder> {
public static final ProtoAdapter<SpriteEntity> ADAPTER = new ProtoAdapter_SpriteEntity();
private static final long serialVersionUID = 0L;
public static final String DEFAULT_IMAGEKEY = "";
public static final String DEFAULT_MATTEKEY = "";
/**
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String imageKey;
/**
* 帧列表
*/
@WireField(
tag = 2,
adapter = "com.opensource.svgaplayer.proto.FrameEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<FrameEntity> frames;
/**
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
*/
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String matteKey;
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey) {
this(imageKey, frames, matteKey, ByteString.EMPTY);
}
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.imageKey = imageKey;
this.frames = Internal.immutableCopyOf("frames", frames);
this.matteKey = matteKey;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.imageKey = imageKey;
builder.frames = Internal.copyOf("frames", frames);
builder.matteKey = matteKey;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof SpriteEntity)) return false;
SpriteEntity o = (SpriteEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(imageKey, o.imageKey)
&& frames.equals(o.frames)
&& Internal.equals(matteKey, o.matteKey);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (imageKey != null ? imageKey.hashCode() : 0);
result = result * 37 + frames.hashCode();
result = result * 37 + (matteKey != null ? matteKey.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (imageKey != null) builder.append(", imageKey=").append(imageKey);
if (!frames.isEmpty()) builder.append(", frames=").append(frames);
if (matteKey != null) builder.append(", matteKey=").append(matteKey);
return builder.replace(0, 2, "SpriteEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<SpriteEntity, Builder> {
public String imageKey;
public List<FrameEntity> frames;
public String matteKey;
public Builder() {
frames = Internal.newMutableList();
}
/**
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
*/
public Builder imageKey(String imageKey) {
this.imageKey = imageKey;
return this;
}
/**
* 帧列表
*/
public Builder frames(List<FrameEntity> frames) {
Internal.checkElementsNotNull(frames);
this.frames = frames;
return this;
}
/**
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
*/
public Builder matteKey(String matteKey) {
this.matteKey = matteKey;
return this;
}
@Override
public SpriteEntity build() {
return new SpriteEntity(imageKey, frames, matteKey, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_SpriteEntity extends ProtoAdapter<SpriteEntity> {
ProtoAdapter_SpriteEntity() {
super(FieldEncoding.LENGTH_DELIMITED, SpriteEntity.class);
}
@Override
public int encodedSize(SpriteEntity value) {
return (value.imageKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.imageKey) : 0)
+ FrameEntity.ADAPTER.asRepeated().encodedSizeWithTag(2, value.frames)
+ (value.matteKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(3, value.matteKey) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, SpriteEntity value) throws IOException {
if (value.imageKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.imageKey);
FrameEntity.ADAPTER.asRepeated().encodeWithTag(writer, 2, value.frames);
if (value.matteKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 3, value.matteKey);
writer.writeBytes(value.unknownFields());
}
@Override
public SpriteEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.imageKey(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.frames.add(FrameEntity.ADAPTER.decode(reader)); break;
case 3: builder.matteKey(ProtoAdapter.STRING.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public SpriteEntity redact(SpriteEntity value) {
Builder builder = value.newBuilder();
Internal.redactElements(builder.frames, FrameEntity.ADAPTER);
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,251 +0,0 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 34:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class Transform extends Message<Transform, Transform.Builder> {
public static final ProtoAdapter<Transform> ADAPTER = new ProtoAdapter_Transform();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_A = 0.0f;
public static final Float DEFAULT_B = 0.0f;
public static final Float DEFAULT_C = 0.0f;
public static final Float DEFAULT_D = 0.0f;
public static final Float DEFAULT_TX = 0.0f;
public static final Float DEFAULT_TY = 0.0f;
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float a;
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float b;
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float c;
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float d;
@WireField(
tag = 5,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float tx;
@WireField(
tag = 6,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float ty;
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty) {
this(a, b, c, d, tx, ty, ByteString.EMPTY);
}
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.tx = tx;
this.ty = ty;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.a = a;
builder.b = b;
builder.c = c;
builder.d = d;
builder.tx = tx;
builder.ty = ty;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Transform)) return false;
Transform o = (Transform) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(a, o.a)
&& Internal.equals(b, o.b)
&& Internal.equals(c, o.c)
&& Internal.equals(d, o.d)
&& Internal.equals(tx, o.tx)
&& Internal.equals(ty, o.ty);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (a != null ? a.hashCode() : 0);
result = result * 37 + (b != null ? b.hashCode() : 0);
result = result * 37 + (c != null ? c.hashCode() : 0);
result = result * 37 + (d != null ? d.hashCode() : 0);
result = result * 37 + (tx != null ? tx.hashCode() : 0);
result = result * 37 + (ty != null ? ty.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (a != null) builder.append(", a=").append(a);
if (b != null) builder.append(", b=").append(b);
if (c != null) builder.append(", c=").append(c);
if (d != null) builder.append(", d=").append(d);
if (tx != null) builder.append(", tx=").append(tx);
if (ty != null) builder.append(", ty=").append(ty);
return builder.replace(0, 2, "Transform{").append('}').toString();
}
public static final class Builder extends Message.Builder<Transform, Builder> {
public Float a;
public Float b;
public Float c;
public Float d;
public Float tx;
public Float ty;
public Builder() {
}
public Builder a(Float a) {
this.a = a;
return this;
}
public Builder b(Float b) {
this.b = b;
return this;
}
public Builder c(Float c) {
this.c = c;
return this;
}
public Builder d(Float d) {
this.d = d;
return this;
}
public Builder tx(Float tx) {
this.tx = tx;
return this;
}
public Builder ty(Float ty) {
this.ty = ty;
return this;
}
@Override
public Transform build() {
return new Transform(a, b, c, d, tx, ty, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_Transform extends ProtoAdapter<Transform> {
ProtoAdapter_Transform() {
super(FieldEncoding.LENGTH_DELIMITED, Transform.class);
}
@Override
public int encodedSize(Transform value) {
return (value.a != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.a) : 0)
+ (value.b != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.b) : 0)
+ (value.c != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.c) : 0)
+ (value.d != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.d) : 0)
+ (value.tx != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(5, value.tx) : 0)
+ (value.ty != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(6, value.ty) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, Transform value) throws IOException {
if (value.a != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.a);
if (value.b != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.b);
if (value.c != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.c);
if (value.d != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.d);
if (value.tx != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 5, value.tx);
if (value.ty != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 6, value.ty);
writer.writeBytes(value.unknownFields());
}
@Override
public Transform decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.a(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.b(ProtoAdapter.FLOAT.decode(reader)); break;
case 3: builder.c(ProtoAdapter.FLOAT.decode(reader)); break;
case 4: builder.d(ProtoAdapter.FLOAT.decode(reader)); break;
case 5: builder.tx(ProtoAdapter.FLOAT.decode(reader)); break;
case 6: builder.ty(ProtoAdapter.FLOAT.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public Transform redact(Transform value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -1,102 +0,0 @@
package com.opensource.svgaplayer.utils
/**
* Helper class for creating pools of objects. An example use looks like this:
* <pre>
* public class MyPooledClass {
*
* private static final SynchronizedPool<MyPooledClass> sPool =
* new SynchronizedPool<MyPooledClass>(10);
*
* public static MyPooledClass obtain() {
* MyPooledClass instance = sPool.acquire();
* return (instance != null) ? instance : new MyPooledClass();
* }
*
* public void recycle() {
* // Clear state if needed.
* sPool.release(this);
* }
*
* . . .
* }
* </pre>
*
*/
class Pools private constructor() {
/**
* Interface for managing a pool of objects.
*
* @param <T> The pooled type.
*/
interface Pool<T> {
/**
* @return An instance from the pool if such, null otherwise.
*/
fun acquire(): T?
/**
* Release an instance to the pool.
*
* @param instance The instance to release.
* @return Whether the instance was put in the pool.
*
* @throws IllegalStateException If the instance is already in the pool.
*/
fun release(instance: T): Boolean
}
/**
* Simple (non-synchronized) pool of objects.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*
* @param <T> The pooled type.
*/
open class SimplePool<T>(maxPoolSize: Int) : Pool<T> {
private val mPool: Array<Any?>
private var mPoolSize = 0
init {
require(maxPoolSize > 0) { "The max pool size must be > 0" }
mPool = arrayOfNulls(maxPoolSize)
}
@Suppress("UNCHECKED_CAST")
override fun acquire(): T? {
if (mPoolSize > 0) {
val lastPooledIndex = mPoolSize - 1
val instance = mPool[lastPooledIndex] as T?
mPool[lastPooledIndex] = null
mPoolSize--
return instance
}
return null
}
override fun release(instance: T): Boolean {
check(!isInPool(instance)) { "Already in the pool!" }
if (mPoolSize < mPool.size) {
mPool[mPoolSize] = instance
mPoolSize++
return true
}
return false
}
private fun isInPool(instance: T): Boolean {
for (i in 0 until mPoolSize) {
if (mPool[i] === instance) {
return true
}
}
return false
}
}
}

View File

@@ -1,146 +0,0 @@
package com.opensource.svgaplayer.utils
import android.widget.ImageView
/**
* Created by ubt on 2018/1/19.
*/
class SVGAScaleInfo {
var tranFx : Float = 0.0f
var tranFy : Float = 0.0f
var scaleFx : Float = 1.0f
var scaleFy : Float = 1.0f
var ratio = 1.0f
var ratioX = false
private fun resetVar(){
tranFx = 0.0f
tranFy = 0.0f
scaleFx = 1.0f
scaleFy = 1.0f
ratio = 1.0f
ratioX = false
}
fun performScaleType(canvasWidth : Float, canvasHeight: Float, videoWidth : Float, videoHeight : Float, scaleType: ImageView.ScaleType) {
if (canvasWidth == 0.0f || canvasHeight == 0.0f || videoWidth == 0.0f || videoHeight == 0.0f) {
return
}
resetVar()
val canW_vidW_f = (canvasWidth - videoWidth) / 2.0f
val canH_vidH_f = (canvasHeight - videoHeight) / 2.0f
val videoRatio = videoWidth / videoHeight
val canvasRatio = canvasWidth / canvasHeight
val canH_d_vidH = canvasHeight / videoHeight
val canW_d_vidW = canvasWidth / videoWidth
when (scaleType) {
ImageView.ScaleType.CENTER -> {
tranFx = canW_vidW_f
tranFy = canH_vidH_f
}
ImageView.ScaleType.CENTER_CROP -> {
if (videoRatio > canvasRatio) {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
}
else {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
}
}
ImageView.ScaleType.CENTER_INSIDE -> {
if (videoWidth < canvasWidth && videoHeight < canvasHeight) {
tranFx = canW_vidW_f
tranFy = canH_vidH_f
}
else {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
}
}
}
ImageView.ScaleType.FIT_CENTER -> {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
}
}
ImageView.ScaleType.FIT_START -> {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
}
}
ImageView.ScaleType.FIT_END -> {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy= canvasHeight - videoHeight * (canW_d_vidW)
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = canvasWidth - videoWidth * (canH_d_vidH)
}
}
ImageView.ScaleType.FIT_XY -> {
ratio = Math.max(canW_d_vidW, canH_d_vidH)
ratioX = canW_d_vidW > canH_d_vidH
scaleFx = canW_d_vidW
scaleFy = canH_d_vidH
}
else -> {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
}
}
}
}

View File

@@ -1,11 +0,0 @@
package com.opensource.svgaplayer.utils
/**
* Created by cuiminghui on 2017/3/29.
*/
class SVGAPoint(val x: Float, val y: Float, val value: Float)
class SVGARect(val x: Double, val y: Double, val width: Double, val height: Double)
class SVGARange(val location: Int, val length: Int)

View File

@@ -1,28 +0,0 @@
package com.opensource.svgaplayer.utils.log
import android.util.Log
/**
* 内部默认 ILogger 接口实现
*/
class DefaultLogCat : ILogger {
override fun verbose(tag: String, msg: String) {
Log.v(tag, msg)
}
override fun info(tag: String, msg: String) {
Log.i(tag, msg)
}
override fun debug(tag: String, msg: String) {
Log.d(tag, msg)
}
override fun warn(tag: String, msg: String) {
Log.w(tag, msg)
}
override fun error(tag: String, msg: String?, error: Throwable?) {
Log.e(tag, msg, error)
}
}

View File

@@ -1,12 +0,0 @@
package com.opensource.svgaplayer.utils.log
/**
* log 外部接管接口
**/
interface ILogger {
fun verbose(tag: String, msg: String)
fun info(tag: String, msg: String)
fun debug(tag: String, msg: String)
fun warn(tag: String, msg: String)
fun error(tag: String, msg: String?, error: Throwable?)
}

View File

@@ -1,57 +0,0 @@
package com.opensource.svgaplayer.utils.log
/**
* 日志输出
*/
internal object LogUtils {
private const val TAG = "SVGALog"
fun verbose(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.verbose(tag, msg)
}
fun info(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.info(tag, msg)
}
fun debug(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.debug(tag, msg)
}
fun warn(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.warn(tag, msg)
}
fun error(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.error(tag, msg, null)
}
fun error(tag: String, error: Throwable) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.error(tag, error.message, error)
}
fun error(tag: String = TAG, msg: String, error: Throwable) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.error(tag, msg, error)
}
}

View File

@@ -1,40 +0,0 @@
package com.opensource.svgaplayer.utils.log
/**
* SVGA logger 配置管理
**/
object SVGALogger {
private var mLogger: ILogger? = DefaultLogCat()
private var isLogEnabled = false
/**
* log 接管注入
*/
fun injectSVGALoggerImp(logImp: ILogger): SVGALogger {
mLogger = logImp
return this
}
/**
* 设置是否开启 log
*/
fun setLogEnabled(isEnabled: Boolean): SVGALogger {
isLogEnabled = isEnabled
return this
}
/**
* 获取当前 ILogger 实现类
*/
fun getSVGALogger(): ILogger? {
return mLogger
}
/**
* 是否开启 log
*/
fun isLogEnabled(): Boolean {
return isLogEnabled
}
}

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SVGAImageView">
<attr name="source" format="string" />
<attr name="autoPlay" format="boolean" />
<attr name="antiAlias" format="boolean" />
<attr name="loopCount" format="integer" />
<attr name="clearsAfterStop" format="boolean" />
<attr name="clearsAfterDetached" format="boolean" />
<attr name="fillMode" format="enum">
<enum name="Backward" value="0" />
<enum name="Forward" value="1" />
<enum name="Clear" value="2"/>
</attr>
</declare-styleable>
</resources>

View File

@@ -1,3 +0,0 @@
<resources>
<string name="app_name">SVGAPlayer</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="dialog_share_title">Share</string>
<string name="dialog_share_info">Come and watch %s live on PDLIVE and meet more interesting people!</string>
<string name="dialog_invite_title">Invite Friends</string>
<string name="dialog_invite_info">Come to PDLIVE to discover more and better live streams.</string>
<string name="dialog_share_copy">Copy</string>
</resources>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="com.twitter.sdk.android.CONSUMER_KEY" >ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
<string name="com.twitter.sdk.android.CONSUMER_SECRET">aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
<string name="dialog_share_title">分享</string>
<string name="dialog_share_info">快來 PDLIVE觀看%s直播認識更多有趣的朋友吧</string>
<string name="dialog_share_app_facebook" >Facebook</string>
<string name="dialog_share_app_line" >Line</string>
<string name="dialog_share_app_twitter">Twitter</string>
<string name="dialog_share_app_whatsapp" >WhatsApp</string>
<string name="dialog_share_app_messenger">Messenger</string>
<string name="dialog_share_app_instagram" >Instagram</string>
<string name="dialog_invite_title">邀請好友</string>
<string name="dialog_invite_info">快來 PDLIVE觀看直播認識更多有趣的朋友吧</string>
<string name="dialog_share_copy">複製</string>
</resources>

View File

@@ -1,16 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="com.twitter.sdk.android.CONSUMER_KEY" >ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
<string name="com.twitter.sdk.android.CONSUMER_SECRET" >aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
<string name="dialog_share_app_facebook" >Facebook</string>
<string name="dialog_share_app_line" >Line</string>
<string name="dialog_share_app_twitter">Twitter</string>
<string name="dialog_share_app_whatsapp" >WhatsApp</string>
<string name="dialog_share_app_messenger">Messenger</string>
<string name="dialog_share_app_instagram" >Instagram</string>
<string name="dialog_share_title">Share</string>
<string name="dialog_share_info">Come and watch %s live on PDLIVE and meet more interesting people!</string>
<string name="dialog_invite_title">Invite Friends</string>
<string name="dialog_invite_info">Come to PDLIVE to discover more and better live streams.</string>
<string name="dialog_share_copy">Copy</string>
<string name="com.twitter.sdk.android.CONSUMER_KEY" translatable="false">ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
<string name="com.twitter.sdk.android.CONSUMER_SECRET" translatable="false">aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
<string name="dialog_share_title">分享</string>
<string name="dialog_share_info">快來 PDLIVE觀看%s直播認識更多有趣的朋友吧</string>
<string name="dialog_share_app_facebook" translatable="false">Facebook</string>
<string name="dialog_share_app_line" translatable="false">Line</string>
<string name="dialog_share_app_twitter" translatable="false">Twitter</string>
<string name="dialog_share_app_whatsapp" translatable="false">WhatsApp</string>
<string name="dialog_share_app_messenger" translatable="false">Messenger</string>
<string name="dialog_share_app_instagram" translatable="false">Instagram</string>
<string name="dialog_invite_title">邀請好友</string>
<string name="dialog_invite_info">快來 PDLIVE觀看直播認識更多有趣的朋友吧</string>
<string name="dialog_share_copy">複製</string>
</resources>

View File

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

View File

@@ -1,37 +0,0 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "com.angcyo.tablayout"
compileSdk rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
manifestPlaceholders = rootProject.ext.manifestPlaceholders
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
}
//apply from: "$gradleHost/master/publish.gradle"

View File

@@ -1,200 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.*
import android.graphics.drawable.Drawable
import android.text.TextPaint
import android.util.AttributeSet
import android.view.View
import androidx.core.view.ViewCompat
/**
* 基础自绘Drawable
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/25
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
abstract class AbsDslDrawable : Drawable() {
companion object {
/**不绘制*/
const val DRAW_TYPE_DRAW_NONE = 0x00
/**[android.view.View.draw]*/
const val DRAW_TYPE_DRAW_AFTER = 0x01
const val DRAW_TYPE_DRAW_BEFORE = 0x02
/**[android.view.View.onDraw]*/
const val DRAW_TYPE_ON_DRAW_AFTER = 0x04
const val DRAW_TYPE_ON_DRAW_BEFORE = 0x08
}
/**画笔*/
val textPaint: TextPaint by lazy {
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
isFilterBitmap = true
style = Paint.Style.FILL
textSize = 12 * dp
}
}
val drawRect = Rect()
val drawRectF = RectF()
/**需要在那个方法中触发绘制*/
var drawType = DRAW_TYPE_ON_DRAW_AFTER
/**xml属性读取*/
open fun initAttribute(
context: Context,
attributeSet: AttributeSet? = null
) {
//val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.xxx)
//typedArray.recycle()
}
//<editor-fold desc="View相关方法">
/**附着的[View]*/
val attachView: View?
get() = if (callback is View) callback as? View else null
val isInEditMode: Boolean
get() = attachView?.isInEditMode ?: false
val paddingLeft: Int
get() = attachView?.paddingLeft ?: 0
val paddingRight: Int
get() = attachView?.paddingRight ?: 0
val paddingTop: Int
get() = attachView?.paddingTop ?: 0
val paddingBottom: Int
get() = attachView?.paddingBottom ?: 0
val viewHeight: Int
get() = attachView?.measuredHeight ?: 0
val viewWidth: Int
get() = attachView?.measuredWidth ?: 0
val viewDrawHeight: Int
get() = viewHeight - paddingTop - paddingBottom
val viewDrawWidth: Int
get() = viewWidth - paddingLeft - paddingRight
val isViewRtl: Boolean
get() = attachView != null && ViewCompat.getLayoutDirection(attachView!!) == ViewCompat.LAYOUT_DIRECTION_RTL
//</editor-fold desc="View相关方法">
//<editor-fold desc="基类方法">
/**核心方法, 绘制*/
override fun draw(canvas: Canvas) {
}
override fun getIntrinsicWidth(): Int {
return super.getIntrinsicWidth()
}
override fun getMinimumWidth(): Int {
return super.getMinimumWidth()
}
override fun getIntrinsicHeight(): Int {
return super.getIntrinsicHeight()
}
override fun getMinimumHeight(): Int {
return super.getMinimumHeight()
}
override fun setAlpha(alpha: Int) {
if (textPaint.alpha != alpha) {
textPaint.alpha = alpha
invalidateSelf()
}
}
override fun getAlpha(): Int {
return textPaint.alpha
}
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setBounds(left, top, right, bottom)
}
override fun setBounds(bounds: Rect) {
super.setBounds(bounds)
}
//不透明度
override fun getOpacity(): Int {
return if (alpha < 255) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
}
override fun getColorFilter(): ColorFilter? {
return textPaint.colorFilter
}
override fun setColorFilter(colorFilter: ColorFilter?) {
textPaint.colorFilter = colorFilter
invalidateSelf()
}
override fun mutate(): Drawable {
return super.mutate()
}
override fun setDither(dither: Boolean) {
textPaint.isDither = dither
invalidateSelf()
}
override fun setFilterBitmap(filter: Boolean) {
textPaint.isFilterBitmap = filter
invalidateSelf()
}
override fun isFilterBitmap(): Boolean {
return textPaint.isFilterBitmap
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
}
override fun onLevelChange(level: Int): Boolean {
return super.onLevelChange(level)
}
override fun onStateChange(state: IntArray): Boolean {
return super.onStateChange(state)
}
override fun setTintList(tint: ColorStateList?) {
super.setTintList(tint)
}
override fun setTintMode(tintMode: PorterDuff.Mode?) {
super.setTintMode(tintMode)
}
override fun setTintBlendMode(blendMode: BlendMode?) {
super.setTintBlendMode(blendMode)
}
override fun setHotspot(x: Float, y: Float) {
super.setHotspot(x, y)
}
override fun setHotspotBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setHotspotBounds(left, top, right, bottom)
}
override fun getHotspotBounds(outRect: Rect) {
super.getHotspotBounds(outRect)
}
//</editor-fold desc="基类方法">
}

View File

@@ -1,275 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.TextUtils
import android.util.AttributeSet
import android.view.Gravity
import kotlin.math.max
/**
* 未读数, 未读小红点, 角标绘制Drawable
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/13
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslBadgeDrawable : DslGradientDrawable() {
val dslGravity = DslGravity()
/**重力*/
var badgeGravity: Int = Gravity.CENTER //Gravity.TOP or Gravity.RIGHT
/**角标文本颜色*/
var badgeTextColor = Color.WHITE
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
* null 不绘制角标
* "" 空字符绘制圆点
* 其他 正常绘制
* */
var badgeText: String? = null
/**角标的文本大小*/
var badgeTextSize: Float = 12 * dp
set(value) {
field = value
textPaint.textSize = field
}
/**当[badgeText]只有1个字符时, 使用圆形背景*/
var badgeAutoCircle: Boolean = true
/**圆点状态时的半径大小*/
var badgeCircleRadius = 4 * dpi
/**原点状态下, 单独配置的偏移*/
var badgeCircleOffsetX: Int = 0
var badgeCircleOffsetY: Int = 0
/**额外偏移距离, 会根据[Gravity]自动取负值*/
var badgeOffsetX: Int = 0
var badgeOffsetY: Int = 0
/**文本偏移*/
var badgeTextOffsetX: Int = 0
var badgeTextOffsetY: Int = 0
/**圆点状态时无效*/
var badgePaddingLeft = 0
var badgePaddingRight = 0
var badgePaddingTop = 0
var badgePaddingBottom = 0
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
var badgeMinHeight = -2
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
* -1 表示使用使用计算出来的高度值*/
var badgeMinWidth = -2
//计算属性
val textWidth: Float
get() = textPaint.textWidth(badgeText)
//最大的宽度
val maxWidth: Int
get() = max(
textWidth.toInt(),
originDrawable?.minimumWidth ?: 0
) + badgePaddingLeft + badgePaddingRight
//最大的高度
val maxHeight: Int
get() = max(
textHeight.toInt(),
originDrawable?.minimumHeight ?: 0
) + badgePaddingTop + badgePaddingBottom
val textHeight: Float
get() = textPaint.textHeight()
//原型状态
val isCircle: Boolean
get() = TextUtils.isEmpty(badgeText)
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
super.initAttribute(context, attributeSet)
updateOriginDrawable()
}
override fun draw(canvas: Canvas) {
//super.draw(canvas)
if (badgeText == null) {
return
}
with(dslGravity) {
gravity = if (isViewRtl) {
when (badgeGravity) {
Gravity.RIGHT -> {
Gravity.LEFT
}
Gravity.LEFT -> {
Gravity.RIGHT
}
else -> {
badgeGravity
}
}
} else {
badgeGravity
}
setGravityBounds(bounds)
if (isCircle) {
gravityOffsetX = badgeCircleOffsetX
gravityOffsetY = badgeCircleOffsetY
} else {
gravityOffsetX = badgeOffsetX
gravityOffsetY = badgeOffsetY
}
val textWidth = textPaint.textWidth(badgeText)
val textHeight = textPaint.textHeight()
val drawHeight = if (isCircle) {
badgeCircleRadius.toFloat()
} else {
val height = textHeight + badgePaddingTop + badgePaddingBottom
if (badgeMinHeight > 0) {
max(height, badgeMinHeight.toFloat())
} else {
height
}
}
val drawWidth = if (isCircle) {
badgeCircleRadius.toFloat()
} else {
val width = textWidth + badgePaddingLeft + badgePaddingRight
if (badgeMinWidth == -1) {
max(width, drawHeight)
} else if (badgeMinWidth > 0) {
max(width, badgeMinWidth.toFloat())
} else {
width
}
}
applyGravity(drawWidth, drawHeight) { centerX, centerY ->
if (isCircle) {
textPaint.color = gradientSolidColor
//圆心计算
val cx: Float
val cy: Float
if (gravity.isCenter()) {
cx = centerX.toFloat()
cy = centerY.toFloat()
} else {
cx = centerX.toFloat() + _gravityOffsetX
cy = centerY.toFloat() + _gravityOffsetY
}
//绘制圆
textPaint.color = gradientSolidColor
canvas.drawCircle(
cx,
cy,
badgeCircleRadius.toFloat(),
textPaint
)
//圆的描边
if (gradientStrokeWidth > 0 && gradientStrokeColor != Color.TRANSPARENT) {
val oldWidth = textPaint.strokeWidth
val oldStyle = textPaint.style
textPaint.color = gradientStrokeColor
textPaint.strokeWidth = gradientStrokeWidth.toFloat()
textPaint.style = Paint.Style.STROKE
canvas.drawCircle(
cx,
cy,
badgeCircleRadius.toFloat(),
textPaint
)
textPaint.strokeWidth = oldWidth
textPaint.style = oldStyle
}
} else {
textPaint.color = badgeTextColor
val textDrawX: Float = centerX - textWidth / 2
val textDrawY: Float = centerY + textHeight / 2
val bgLeft = _gravityLeft
val bgTop = _gravityTop
//绘制背景
if (badgeAutoCircle && badgeText?.length == 1) {
if (gradientSolidColor != Color.TRANSPARENT) {
textPaint.color = gradientSolidColor
canvas.drawCircle(
centerX.toFloat(),
centerY.toFloat(),
max(maxWidth, maxHeight).toFloat() / 2,
textPaint
)
}
} else {
originDrawable?.apply {
setBounds(
bgLeft, bgTop,
(bgLeft + drawWidth).toInt(),
(bgTop + drawHeight).toInt()
)
draw(canvas)
}
}
//绘制文本
textPaint.color = badgeTextColor
canvas.drawText(
badgeText!!,
textDrawX + badgeTextOffsetX,
textDrawY - textPaint.descent() + badgeTextOffsetY,
textPaint
)
}
}
}
}
override fun getIntrinsicWidth(): Int {
val width = if (isCircle) {
badgeCircleRadius * 2
} else if (badgeAutoCircle && badgeText?.length == 1) {
max(maxWidth, maxHeight)
} else {
maxWidth
}
return max(badgeMinWidth, width)
}
override fun getIntrinsicHeight(): Int {
val height = if (isCircle) {
badgeCircleRadius * 2
} else if (badgeAutoCircle && badgeText?.length == 1) {
max(maxWidth, maxHeight)
} else {
maxHeight
}
return max(badgeMinHeight, height)
}
}

View File

@@ -1,306 +0,0 @@
package com.angcyo.tablayout
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import androidx.annotation.IntDef
import java.util.*
/**
* 用来构建GradientDrawable
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/27
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslGradientDrawable : AbsDslDrawable() {
/**形状*/
@Shape
var gradientShape = GradientDrawable.RECTANGLE
/**填充的颜色*/
var gradientSolidColor = Color.TRANSPARENT
/**边框的颜色*/
var gradientStrokeColor = Color.TRANSPARENT
/**边框的宽度*/
var gradientStrokeWidth = 0
/**蚂蚁线的宽度*/
var gradientDashWidth = 0f
/**蚂蚁线之间的间距*/
var gradientDashGap = 0f
/**
* 四个角, 8个设置点的圆角信息
* 从 左上y轴->左上x轴->右上x轴->右上y轴..., 开始设置.
*/
var gradientRadii = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
/**颜色渐变*/
var gradientColors: IntArray? = null
var gradientColorsOffsets: FloatArray? = null
/**渐变中心点坐标*/
var gradientCenterX = 0.5f
var gradientCenterY = 0.5f
/**渐变半径, 非比例值, 是px值. [GradientDrawable.RADIAL_GRADIENT]类型才有效*/
var gradientRadius = 0.5f
/** 渐变方向, 默认从左到右 */
var gradientOrientation = GradientDrawable.Orientation.LEFT_RIGHT
/** 渐变类型 */
@GradientType
var gradientType = GradientDrawable.LINEAR_GRADIENT
/**真正绘制的[Drawable]*/
var originDrawable: Drawable? = null
/**宽度补偿*/
var gradientWidthOffset: Int = 0
/**高度补偿*/
var gradientHeightOffset: Int = 0
/**当前的配置, 是否能生成有效的[GradientDrawable]*/
open fun isValidConfig(): Boolean {
return gradientSolidColor != Color.TRANSPARENT ||
gradientStrokeColor != Color.TRANSPARENT ||
gradientColors != null
}
fun _fillRadii(array: FloatArray, radii: String?) {
if (radii.isNullOrEmpty()) {
return
}
val split = radii.split(",")
if (split.size != 8) {
throw IllegalArgumentException("radii 需要8个值.")
} else {
val dp = Resources.getSystem().displayMetrics.density
for (i in split.indices) {
array[i] = split[i].toFloat() * dp
}
}
}
fun fillRadii(radius: Float) {
Arrays.fill(gradientRadii, radius)
}
fun fillRadii(radius: Int) {
_fillRadii(gradientRadii, radius.toFloat())
}
fun _fillRadii(array: FloatArray, radius: Float) {
Arrays.fill(array, radius)
}
fun _fillRadii(array: FloatArray, radius: Int) {
_fillRadii(array, radius.toFloat())
}
fun _fillColor(colors: String?): IntArray? {
if (colors.isNullOrEmpty()) {
return null
}
val split = colors.split(",")
return IntArray(split.size) {
val str = split[it]
if (str.startsWith("#")) {
Color.parseColor(str)
} else {
str.toInt()
}
}
}
/**构建或者更新[originDrawable]*/
open fun updateOriginDrawable(): GradientDrawable? {
val drawable: GradientDrawable? = when (originDrawable) {
null -> GradientDrawable()
is GradientDrawable -> originDrawable as GradientDrawable
else -> {
null
}
}
drawable?.apply {
bounds = this@DslGradientDrawable.bounds
shape = gradientShape
setStroke(
gradientStrokeWidth,
gradientStrokeColor,
gradientDashWidth,
gradientDashGap
)
setColor(gradientSolidColor)
cornerRadii = gradientRadii
if (gradientColors != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setGradientCenter(
this@DslGradientDrawable.gradientCenterX,
this@DslGradientDrawable.gradientCenterY
)
}
gradientRadius = this@DslGradientDrawable.gradientRadius
gradientType = this@DslGradientDrawable.gradientType
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
orientation = gradientOrientation
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setColors(gradientColors, gradientColorsOffsets)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
colors = gradientColors
}
}
originDrawable = this
invalidateSelf()
}
return drawable
}
open fun configDrawable(config: DslGradientDrawable.() -> Unit): DslGradientDrawable {
this.config()
updateOriginDrawable()
return this
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
originDrawable?.apply {
setBounds(
this@DslGradientDrawable.bounds.left - gradientWidthOffset / 2,
this@DslGradientDrawable.bounds.top - gradientHeightOffset / 2,
this@DslGradientDrawable.bounds.right + gradientWidthOffset / 2,
this@DslGradientDrawable.bounds.bottom + gradientHeightOffset / 2
)
draw(canvas)
}
}
//<editor-fold desc="圆角相关配置">
/**
* 4个角, 8个点 圆角配置
*/
fun cornerRadii(radii: FloatArray) {
gradientRadii = radii
}
fun cornerRadius(radii: Float) {
Arrays.fill(gradientRadii, radii)
}
fun cornerRadius(
leftTop: Float = 0f,
rightTop: Float = 0f,
rightBottom: Float = 0f,
leftBottom: Float = 0f
) {
gradientRadii[0] = leftTop
gradientRadii[1] = leftTop
gradientRadii[2] = rightTop
gradientRadii[3] = rightTop
gradientRadii[4] = rightBottom
gradientRadii[5] = rightBottom
gradientRadii[6] = leftBottom
gradientRadii[7] = leftBottom
}
/**
* 只配置左边的圆角
*/
fun cornerRadiiLeft(radii: Float) {
gradientRadii[0] = radii
gradientRadii[1] = radii
gradientRadii[6] = radii
gradientRadii[7] = radii
}
fun cornerRadiiRight(radii: Float) {
gradientRadii[2] = radii
gradientRadii[3] = radii
gradientRadii[4] = radii
gradientRadii[5] = radii
}
fun cornerRadiiTop(radii: Float) {
gradientRadii[0] = radii
gradientRadii[1] = radii
gradientRadii[2] = radii
gradientRadii[3] = radii
}
fun cornerRadiiBottom(radii: Float) {
gradientRadii[4] = radii
gradientRadii[5] = radii
gradientRadii[6] = radii
gradientRadii[7] = radii
}
//</editor-fold desc="圆角相关配置">
//<editor-fold desc="传递属性">
override fun setColorFilter(colorFilter: ColorFilter?) {
super.setColorFilter(colorFilter)
originDrawable?.colorFilter = colorFilter
}
override fun setTintList(tint: ColorStateList?) {
super.setTintList(tint)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
originDrawable?.setTintList(tint)
}
}
override fun setState(stateSet: IntArray): Boolean {
return originDrawable?.setState(stateSet) ?: super.setState(stateSet)
}
override fun getState(): IntArray {
return originDrawable?.state ?: super.getState()
}
//</editor-fold desc="传递属性">
}
@IntDef(
GradientDrawable.RECTANGLE,
GradientDrawable.OVAL,
GradientDrawable.LINE,
GradientDrawable.RING
)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class Shape
@IntDef(
GradientDrawable.LINEAR_GRADIENT,
GradientDrawable.RADIAL_GRADIENT,
GradientDrawable.SWEEP_GRADIENT
)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class GradientType
/**快速创建[GradientDrawable]*/
fun dslGradientDrawable(action: DslGradientDrawable.() -> Unit): GradientDrawable {
return DslGradientDrawable().run {
action()
updateOriginDrawable()!!
}
}

View File

@@ -1,215 +0,0 @@
package com.angcyo.tablayout
import android.graphics.Rect
import android.graphics.RectF
import android.view.Gravity
/**
* [android.view.Gravity] 辅助计算类
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/13
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
class DslGravity {
/**束缚范围*/
val gravityBounds: RectF = RectF()
/**束缚重力*/
var gravity: Int = Gravity.LEFT or Gravity.TOP
/**使用中心坐标作为定位参考, 否则就是四条边*/
var gravityRelativeCenter: Boolean = true
/**额外偏移距离, 会根据[Gravity]自动取负值*/
var gravityOffsetX: Int = 0
var gravityOffsetY: Int = 0
fun setGravityBounds(rectF: RectF) {
gravityBounds.set(rectF)
}
fun setGravityBounds(rect: Rect) {
gravityBounds.set(rect)
}
fun setGravityBounds(
left: Int,
top: Int,
right: Int,
bottom: Int
) {
gravityBounds.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
}
fun setGravityBounds(
left: Float,
top: Float,
right: Float,
bottom: Float
) {
gravityBounds.set(left, top, right, bottom)
}
//计算后的属性
var _horizontalGravity: Int = Gravity.LEFT
var _verticalGravity: Int = Gravity.TOP
var _isCenterGravity: Boolean = false
var _targetWidth = 0f
var _targetHeight = 0f
var _gravityLeft = 0
var _gravityTop = 0
var _gravityRight = 0
var _gravityBottom = 0
//根据gravity调整后的offset
var _gravityOffsetX = 0
var _gravityOffsetY = 0
/**根据[gravity]返回在[gravityBounds]中的[left] [top]位置*/
fun applyGravity(
width: Float = _targetWidth,
height: Float = _targetHeight,
callback: (centerX: Int, centerY: Int) -> Unit
) {
_targetWidth = width
_targetHeight = height
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)
val verticalGravity = gravity and Gravity.VERTICAL_GRAVITY_MASK
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
//调整offset
_gravityOffsetX = when (horizontalGravity) {
Gravity.RIGHT -> -gravityOffsetX
Gravity.END -> -gravityOffsetX
else -> gravityOffsetX
}
_gravityOffsetY = when (verticalGravity) {
Gravity.BOTTOM -> -gravityOffsetY
else -> gravityOffsetY
}
//计算居中的坐标
val centerX = when (horizontalGravity) {
Gravity.CENTER_HORIZONTAL -> (gravityBounds.left + gravityBounds.width() / 2 + _gravityOffsetX).toInt()
Gravity.RIGHT -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
Gravity.END -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
else -> (gravityBounds.left + _gravityOffsetX + if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
}
val centerY = when (verticalGravity) {
Gravity.CENTER_VERTICAL -> (gravityBounds.top + gravityBounds.height() / 2 + _gravityOffsetY).toInt()
Gravity.BOTTOM -> (gravityBounds.bottom + _gravityOffsetY - if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
else -> (gravityBounds.top + _gravityOffsetY + if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
}
//缓存
_horizontalGravity = horizontalGravity
_verticalGravity = verticalGravity
_isCenterGravity = horizontalGravity == Gravity.CENTER_HORIZONTAL &&
verticalGravity == Gravity.CENTER_VERTICAL
_gravityLeft = (centerX - _targetWidth / 2).toInt()
_gravityRight = (centerX + _targetWidth / 2).toInt()
_gravityTop = (centerY - _targetHeight / 2).toInt()
_gravityBottom = (centerY + _targetHeight / 2).toInt()
//回调
callback(centerX, centerY)
}
}
/**
* 默认计算出的是目标中心点坐标参考距离
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
* */
fun dslGravity(
rect: RectF, //计算的矩形
gravity: Int, //重力
width: Float, //放置目标的宽度
height: Float, //放置目标的高度
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
offsetY: Int = 0,
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
): DslGravity {
val _dslGravity = DslGravity()
_dslGravity.setGravityBounds(rect)
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
return _dslGravity
}
/**
* 默认计算出的是目标中心点坐标参考距离
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
* */
fun dslGravity(
rect: Rect, //计算的矩形
gravity: Int, //重力
width: Float, //放置目标的宽度
height: Float, //放置目标的高度
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
offsetY: Int = 0,
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
): DslGravity {
val _dslGravity = DslGravity()
_dslGravity.setGravityBounds(rect)
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
return _dslGravity
}
private fun _config(
_dslGravity: DslGravity,
gravity: Int, //重力
width: Float, //放置目标的宽度
height: Float, //放置目标的高度
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
offsetY: Int = 0,
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
) {
_dslGravity.gravity = gravity
_dslGravity.gravityOffsetX = offsetX
_dslGravity.gravityOffsetY = offsetY
_dslGravity.applyGravity(width, height) { centerX, centerY ->
callback(_dslGravity, centerX, centerY)
}
}
/**居中*/
fun Int.isCenter(): Boolean {
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
return verticalGravity == Gravity.CENTER_VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL
}
fun Int.isLeft(): Boolean {
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
return horizontalGravity == Gravity.LEFT
}
fun Int.isRight(): Boolean {
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
return horizontalGravity == Gravity.RIGHT
}
fun Int.isTop(): Boolean {
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
return verticalGravity == Gravity.TOP
}
fun Int.isBottom(): Boolean {
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
return verticalGravity == Gravity.BOTTOM
}

View File

@@ -1,438 +0,0 @@
package com.angcyo.tablayout
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
/**
* 用来操作[ViewGroup]中的[child], 支持单选, 多选, 拦截.
* 操作的都是可见性为[VISIBLE]的[View]
*
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/24
*/
open class DslSelector {
var parent: ViewGroup? = null
var dslSelectorConfig: DslSelectorConfig = DslSelectorConfig()
//可见view列表
val visibleViewList: MutableList<View> = mutableListOf()
/**
* 选中的索引列表
* */
val selectorIndexList: MutableList<Int> = mutableListOf()
get() {
field.clear()
visibleViewList.forEachIndexed { index, view ->
if (view.isSe()) {
field.add(index)
}
}
return field
}
/**
* 选中的View列表
* */
val selectorViewList: MutableList<View> = mutableListOf()
get() {
field.clear()
visibleViewList.forEachIndexed { index, view ->
if (view.isSe() || index == dslSelectIndex) {
field.add(view)
}
}
return field
}
//child 点击事件处理
val _onChildClickListener = View.OnClickListener {
val index = visibleViewList.indexOf(it)
val select =
if (dslSelectorConfig.dslMultiMode || dslSelectorConfig.dslMinSelectLimit < 1) {
!it.isSe()
} else {
true
}
if (!interceptSelector(index, select, true)) {
selector(
visibleViewList.indexOf(it),
select,
notify = true,
fromUser = true,
forceNotify = it is CompoundButton && dslSelectorConfig.dslMultiMode
)
}
}
/**兼容[CompoundButton]*/
val _onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
buttonView.isChecked = buttonView.isSelected //恢复状态 不做任何处理, 在[OnClickListener]中处理
/*val index = visibleViewList.indexOf(buttonView)
if (interceptSelector(index, isChecked, false)) {
//拦截了此操作
buttonView.isChecked = !isChecked //恢复状态
}
val selectorViewList = selectorViewList
val sum = selectorViewList.size
//Limit 过滤
if (buttonView.isChecked) {
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
//不允许选择
buttonView.isChecked = false //恢复状态
}
} else {
//取消选择, 检查是否达到了 limit
if (sum < dslSelectorConfig.dslMinSelectLimit) {
//不允许取消选择
buttonView.isChecked = true //恢复状态
}
}
if (isChecked) {
//已经选中了控件
} else {
//已经取消了控件
}*/
}
/**当前选中的索引*/
var dslSelectIndex = -1
/**安装*/
fun install(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
dslSelectIndex = -1
parent = viewGroup
updateVisibleList()
dslSelectorConfig.config()
updateStyle()
updateClickListener()
if (dslSelectIndex in 0 until visibleViewList.size) {
selector(dslSelectIndex)
}
return this
}
/**更新样式*/
fun updateStyle() {
visibleViewList.forEachIndexed { index, view ->
val selector = dslSelectIndex == index || view.isSe()
dslSelectorConfig.onStyleItemView(view, index, selector)
}
}
/**更新child的点击事件*/
fun updateClickListener() {
parent?.apply {
for (i in 0 until childCount) {
getChildAt(i)?.let {
it.setOnClickListener(_onChildClickListener)
if (it is CompoundButton) {
it.setOnCheckedChangeListener(_onCheckedChangeListener)
}
}
}
}
}
/**更新可见视图列表*/
fun updateVisibleList(): List<View> {
visibleViewList.clear()
parent?.apply {
for (i in 0 until childCount) {
getChildAt(i)?.let {
if (it.visibility == View.VISIBLE) {
visibleViewList.add(it)
}
}
}
}
if (dslSelectIndex in visibleViewList.indices) {
if (!visibleViewList[dslSelectIndex].isSe()) {
visibleViewList[dslSelectIndex].setSe(true)
}
} else {
//如果当前选中的索引, 不在可见视图列表中
dslSelectIndex = -1
}
return visibleViewList
}
/**
* 操作单个
* @param index 操作目标的索引值
* @param select 选中 or 取消选中
* @param notify 是否需要通知事件
* @param forceNotify 是否强制通知事件.child使用[CompoundButton]时, 推荐使用
* */
fun selector(
index: Int,
select: Boolean = true,
notify: Boolean = true,
fromUser: Boolean = false,
forceNotify: Boolean = false
) {
val oldSelectorList = selectorIndexList.toList()
val lastSelectorIndex: Int? = oldSelectorList.lastOrNull()
val reselect = !dslSelectorConfig.dslMultiMode &&
oldSelectorList.isNotEmpty() &&
oldSelectorList.contains(index)
var needNotify = _selector(index, select, fromUser) || forceNotify
if (!oldSelectorList.isChange(selectorIndexList)) {
//选中项, 未改变时不通知
needNotify = false
}
if (needNotify || reselect) {
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
if (notify) {
notifySelectChange(lastSelectorIndex ?: -1, reselect, fromUser)
}
}
}
/**选择所有
* [select] true:选择所有, false:取消所有*/
fun selectorAll(
select: Boolean = true,
notify: Boolean = true,
fromUser: Boolean = true
) {
val indexList = visibleViewList.mapIndexedTo(mutableListOf()) { index, _ ->
index
}
selector(indexList, select, notify, fromUser)
}
/**
* 操作多个
* @param select 选中 or 取消选中
* [selector]
* */
fun selector(
indexList: MutableList<Int>,
select: Boolean = true,
notify: Boolean = true,
fromUser: Boolean = false
) {
val oldSelectorIndexList = selectorIndexList
val lastSelectorIndex: Int? = oldSelectorIndexList.lastOrNull()
var result = false
indexList.forEach {
result = _selector(it, select, fromUser) || result
}
if (result) {
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
if (notify) {
notifySelectChange(lastSelectorIndex ?: -1, false, fromUser)
}
}
}
/**通知事件*/
fun notifySelectChange(lastSelectorIndex: Int, reselect: Boolean, fromUser: Boolean) {
val indexSelectorList = selectorIndexList
dslSelectorConfig.onSelectViewChange(
visibleViewList.getOrNull(lastSelectorIndex),
selectorViewList,
reselect,
fromUser
)
dslSelectorConfig.onSelectIndexChange(
lastSelectorIndex,
indexSelectorList,
reselect,
fromUser
)
}
/**当前的操作是否被拦截*/
fun interceptSelector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
val visibleViewList = visibleViewList
if (index !in 0 until visibleViewList.size) {
return true
}
return dslSelectorConfig.onSelectItemView(visibleViewList[index], index, select, fromUser)
}
/**@return 是否发生过改变*/
fun _selector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
val visibleViewList = visibleViewList
//超范围过滤
if (index !in 0 until visibleViewList.size) {
"index out of list.".logi()
return false
}
val selectorIndexList = selectorIndexList
val selectorViewList = selectorViewList
if (selectorIndexList.isNotEmpty()) {
if (select) {
//需要选中某项
if (dslSelectorConfig.dslMultiMode) {
//多选模式
if (selectorIndexList.contains(index)) {
//已经选中
return false
}
} else {
//单选模式
//取消之前选中
selectorIndexList.forEach {
if (it != index) {
visibleViewList[it].setSe(false)
}
}
if (selectorIndexList.contains(index)) {
//已经选中
return true
}
}
} else {
//需要取消选中
if (!selectorIndexList.contains(index)) {
//目标已经是未选中
return false
}
}
}
//Limit 过滤
if (select) {
val sum = selectorViewList.size + 1
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
//不允许选择
return false
}
} else {
//取消选择, 检查是否达到了 limit
val sum = selectorViewList.size - 1
if (sum < dslSelectorConfig.dslMinSelectLimit) {
//不允许取消选择
return false
}
}
val selectorView = visibleViewList[index]
//更新选中样式
selectorView.setSe(select)
if (dslSelectorConfig.dslMultiMode) {
//多选
} else {
//单选
//取消之前
selectorViewList.forEach { view ->
//更新样式
val indexOf = visibleViewList.indexOf(view)
if (indexOf != index &&
!dslSelectorConfig.onSelectItemView(view, indexOf, false, fromUser)
) {
view.setSe(false)
dslSelectorConfig.onStyleItemView(view, indexOf, false)
}
}
}
dslSelectorConfig.onStyleItemView(selectorView, index, select)
return true
}
/**是否选中状态*/
fun View.isSe(): Boolean {
return isSelected || if (this is CompoundButton) isChecked else false
}
fun View.setSe(se: Boolean) {
isSelected = se
if (this is CompoundButton) isChecked = se
}
}
/**
* Dsl配置项
* */
open class DslSelectorConfig {
/**取消选择时, 最小需要保持多个选中. 可以决定单选时, 是否允许取消所有选中*/
var dslMinSelectLimit = 1
/**多选时, 最大允许多个选中*/
var dslMaxSelectLimit = Int.MAX_VALUE
/**是否是多选模式*/
var dslMultiMode: Boolean = false
/**
* 用来初始化[itemView]的样式
* [onSelectItemView]
* */
var onStyleItemView: (itemView: View, index: Int, select: Boolean) -> Unit =
{ _, _, _ ->
}
/**
* 选中[View]改变回调, 优先于[onSelectIndexChange]触发, 区别在于参数类型不一样
* @param fromView 单选模式下有效, 表示之前选中的[View]
* @param reselect 是否是重复选择, 只在单选模式下有效
* @param fromUser 是否是用户产生的回调, 而非代码设置
* */
var onSelectViewChange: (fromView: View?, selectViewList: List<View>, reselect: Boolean, fromUser: Boolean) -> Unit =
{ _, _, _, _ ->
}
/**
* 选中改变回调
* [onSelectViewChange]
* @param fromIndex 单选模式下有效, 表示之前选中的[View], 在可见性[child]列表中的索引
* */
var onSelectIndexChange: (fromIndex: Int, selectIndexList: List<Int>, reselect: Boolean, fromUser: Boolean) -> Unit =
{ fromIndex, selectList, reselect, fromUser ->
"选择:[$fromIndex]->${selectList} reselect:$reselect fromUser:$fromUser".logi()
}
/**
* 当需要选中[itemView]时回调, 返回[true]表示拦截默认处理
* @param itemView 操作的[View]
* @param index [itemView]在可见性view列表中的索引. 非ViewGroup中的索引
* @param select 选中 or 取消选中
* @return true 表示拦截默认处理
* */
var onSelectItemView: (itemView: View, index: Int, select: Boolean, fromUser: Boolean) -> Boolean =
{ _, _, _, _ ->
false
}
}
/**[DslSelector]组件*/
fun dslSelector(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
return DslSelector().apply {
install(viewGroup, config)
}
}

View File

@@ -1,222 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.Gravity
import androidx.annotation.Px
/**
* 角标
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/13
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabBadge : DslBadgeDrawable() {
/**角标默认配置项*/
val defaultBadgeConfig = TabBadgeConfig()
/**预览的角标属性*/
var xmlBadgeText: String? = null
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
gradientSolidColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_badge_solid_color,
defaultBadgeConfig.badgeSolidColor
)
defaultBadgeConfig.badgeSolidColor = gradientSolidColor
badgeTextColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_badge_text_color,
defaultBadgeConfig.badgeTextColor
)
defaultBadgeConfig.badgeTextColor = badgeTextColor
gradientStrokeColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_badge_stroke_color,
defaultBadgeConfig.badgeStrokeColor
)
defaultBadgeConfig.badgeStrokeColor = gradientStrokeColor
gradientStrokeWidth =
typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_stroke_width,
defaultBadgeConfig.badgeStrokeWidth
)
defaultBadgeConfig.badgeStrokeWidth = gradientStrokeWidth
badgeGravity = typedArray.getInt(
R.styleable.DslTabLayout_tab_badge_gravity,
defaultBadgeConfig.badgeGravity
)
defaultBadgeConfig.badgeGravity = badgeGravity
badgeOffsetX = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_offset_x,
defaultBadgeConfig.badgeOffsetX
)
defaultBadgeConfig.badgeOffsetX = badgeOffsetX
badgeOffsetY = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_offset_y,
defaultBadgeConfig.badgeOffsetY
)
defaultBadgeConfig.badgeOffsetY = badgeOffsetY
badgeCircleOffsetX = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_circle_offset_x,
defaultBadgeConfig.badgeOffsetX
)
defaultBadgeConfig.badgeCircleOffsetX = badgeCircleOffsetX
badgeCircleOffsetY = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_circle_offset_y,
defaultBadgeConfig.badgeOffsetY
)
defaultBadgeConfig.badgeCircleOffsetY = badgeCircleOffsetY
badgeCircleRadius = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_circle_radius,
defaultBadgeConfig.badgeCircleRadius
)
defaultBadgeConfig.badgeCircleRadius = badgeCircleRadius
val badgeRadius = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_radius,
defaultBadgeConfig.badgeRadius
)
cornerRadius(badgeRadius.toFloat())
defaultBadgeConfig.badgeRadius = badgeRadius
badgePaddingLeft = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_left,
defaultBadgeConfig.badgePaddingLeft
)
defaultBadgeConfig.badgePaddingLeft = badgePaddingLeft
badgePaddingRight = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_right,
defaultBadgeConfig.badgePaddingRight
)
defaultBadgeConfig.badgePaddingRight = badgePaddingRight
badgePaddingTop = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_top,
defaultBadgeConfig.badgePaddingTop
)
defaultBadgeConfig.badgePaddingTop = badgePaddingTop
badgePaddingBottom = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_bottom,
defaultBadgeConfig.badgePaddingBottom
)
defaultBadgeConfig.badgePaddingBottom = badgePaddingBottom
xmlBadgeText = typedArray.getString(R.styleable.DslTabLayout_tab_badge_text)
badgeTextSize = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_text_size,
defaultBadgeConfig.badgeTextSize.toInt()
).toFloat()
defaultBadgeConfig.badgeTextSize = badgeTextSize
defaultBadgeConfig.badgeAnchorChildIndex =
typedArray.getInteger(
R.styleable.DslTabLayout_tab_badge_anchor_child_index,
defaultBadgeConfig.badgeAnchorChildIndex
)
defaultBadgeConfig.badgeIgnoreChildPadding = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_badge_ignore_child_padding,
defaultBadgeConfig.badgeIgnoreChildPadding
)
defaultBadgeConfig.badgeMinWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_badge_min_width,
defaultBadgeConfig.badgeMinWidth
)
defaultBadgeConfig.badgeMinHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_badge_min_height,
defaultBadgeConfig.badgeMinHeight
)
typedArray.recycle()
super.initAttribute(context, attributeSet)
}
/**使用指定配置, 更新[DslBadgeDrawable]*/
fun updateBadgeConfig(badgeConfig: TabBadgeConfig) {
gradientSolidColor = badgeConfig.badgeSolidColor
gradientStrokeColor = badgeConfig.badgeStrokeColor
gradientStrokeWidth = badgeConfig.badgeStrokeWidth
badgeTextColor = badgeConfig.badgeTextColor
badgeGravity = badgeConfig.badgeGravity
badgeOffsetX = badgeConfig.badgeOffsetX
badgeOffsetY = badgeConfig.badgeOffsetY
badgeCircleOffsetX = badgeConfig.badgeCircleOffsetX
badgeCircleOffsetY = badgeConfig.badgeCircleOffsetY
badgeCircleRadius = badgeConfig.badgeCircleRadius
badgePaddingLeft = badgeConfig.badgePaddingLeft
badgePaddingRight = badgeConfig.badgePaddingRight
badgePaddingTop = badgeConfig.badgePaddingTop
badgePaddingBottom = badgeConfig.badgePaddingBottom
badgeTextSize = badgeConfig.badgeTextSize
cornerRadius(badgeConfig.badgeRadius.toFloat())
badgeMinHeight = badgeConfig.badgeMinHeight
badgeMinWidth = badgeConfig.badgeMinWidth
badgeText = badgeConfig.badgeText
}
}
/**角标绘制参数配置*/
data class TabBadgeConfig(
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
* null 不绘制角标
* "" 空字符绘制圆点
* 其他 正常绘制
* */
var badgeText: String? = null,
/**重力*/
var badgeGravity: Int = Gravity.CENTER,
/**角标背景颜色*/
var badgeSolidColor: Int = Color.RED,
/**角标边框颜色*/
var badgeStrokeColor: Int = Color.TRANSPARENT,
/**角标边框宽度*/
var badgeStrokeWidth: Int = 0,
/**角标文本颜色*/
var badgeTextColor: Int = Color.WHITE,
/**角标文本字体大小*/
@Px
var badgeTextSize: Float = 12 * dp,
/**圆点状态时的半径大小*/
var badgeCircleRadius: Int = 4 * dpi,
/**圆角大小*/
var badgeRadius: Int = 10 * dpi,
/**额外偏移距离, 会根据[Gravity]自动取负值*/
var badgeOffsetX: Int = 0,
var badgeOffsetY: Int = 0,
var badgeCircleOffsetX: Int = 0,
var badgeCircleOffsetY: Int = 0,
/**圆点状态时无效*/
var badgePaddingLeft: Int = 4 * dpi,
var badgePaddingRight: Int = 4 * dpi,
var badgePaddingTop: Int = 0,
var badgePaddingBottom: Int = 0,
var badgeAnchorChildIndex: Int = -1,
var badgeIgnoreChildPadding: Boolean = true,
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
var badgeMinHeight: Int = -2,
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
* -1 表示使用使用计算出来的高度值*/
var badgeMinWidth: Int = -1
)

View File

@@ -1,279 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import androidx.core.view.ViewCompat
/**
* 边框绘制, 支持首尾圆角中间不圆角的样式
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/27
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabBorder : DslGradientDrawable() {
/**
* 是否要接管[itemView]背景的绘制
* [updateItemBackground]
* */
var borderDrawItemBackground: Boolean = true
/**是否保持每个[itemView]的圆角都一样, 否则首尾有圆角, 中间没有圆角*/
var borderKeepItemRadius: Boolean = false
var borderBackgroundDrawable: Drawable? = null
/**宽度补偿*/
var borderBackgroundWidthOffset: Int = 0
/**高度补偿*/
var borderBackgroundHeightOffset: Int = 0
/**强制指定选中item的背景颜色*/
var borderItemBackgroundSolidColor: Int? = null
/**当item不可选中时的背景绘制颜色
* [com.angcyo.tablayout.DslTabLayout.itemEnableSelector]
* [borderItemBackgroundSolidColor]*/
var borderItemBackgroundSolidDisableColor: Int? = null
/**强制指定选中item的背景渐变颜色*/
var borderItemBackgroundGradientColors: IntArray? = null
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
val borderBackgroundColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_border_solid_color, gradientSolidColor)
gradientStrokeColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_stroke_color,
gradientStrokeColor
)
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_border_stroke_width,
2 * dpi
)
val radiusSize =
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_border_radius_size, 0)
cornerRadius(radiusSize.toFloat())
originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_border_drawable)
borderDrawItemBackground = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_border_draw_item_background,
borderDrawItemBackground
)
borderKeepItemRadius = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_border_keep_item_radius,
borderKeepItemRadius
)
borderBackgroundWidthOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_border_item_background_width_offset,
borderBackgroundWidthOffset
)
borderBackgroundHeightOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_border_item_background_height_offset,
borderBackgroundHeightOffset
)
//
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_color)) {
borderItemBackgroundSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_solid_color,
gradientStrokeColor
)
}
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color)) {
borderItemBackgroundSolidDisableColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color,
borderItemBackgroundSolidColor ?: gradientStrokeColor
)
}
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color) ||
typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color)
) {
val startColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color,
gradientStrokeColor
)
val endColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color,
gradientStrokeColor
)
borderItemBackgroundGradientColors = intArrayOf(startColor, endColor)
}
typedArray.recycle()
if (originDrawable == null) {
//无自定义的drawable, 那么自绘.
borderBackgroundDrawable = DslGradientDrawable().configDrawable {
gradientSolidColor = borderBackgroundColor
gradientRadii = this@DslTabBorder.gradientRadii
}.originDrawable
updateOriginDrawable()
}
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
originDrawable?.apply {
setBounds(
paddingLeft,
paddingBottom,
viewWidth - paddingRight,
viewHeight - paddingBottom
)
draw(canvas)
}
}
fun drawBorderBackground(canvas: Canvas) {
super.draw(canvas)
borderBackgroundDrawable?.apply {
setBounds(
paddingLeft,
paddingBottom,
viewWidth - paddingRight,
viewHeight - paddingBottom
)
draw(canvas)
}
}
var itemSelectBgDrawable: Drawable? = null
var itemDeselectBgDrawable: Drawable? = null
/**开启边框绘制后, [itemView]的背景也需要负责设置*/
open fun updateItemBackground(
tabLayout: DslTabLayout,
itemView: View,
index: Int,
select: Boolean
) {
if (!borderDrawItemBackground) {
return
}
if (select) {
val isFirst = index == 0
val isLast = index == tabLayout.dslSelector.visibleViewList.size - 1
val drawable = DslGradientDrawable().configDrawable {
gradientWidthOffset = borderBackgroundWidthOffset
gradientHeightOffset = borderBackgroundHeightOffset
gradientSolidColor =
borderItemBackgroundSolidColor ?: this@DslTabBorder.gradientStrokeColor
if (!tabLayout.itemEnableSelector) {
if (borderItemBackgroundSolidDisableColor != null) {
gradientSolidColor = borderItemBackgroundSolidDisableColor!!
}
}
gradientColors = borderItemBackgroundGradientColors
if ((isFirst && isLast) || borderKeepItemRadius) {
//只有一个child
gradientRadii = this@DslTabBorder.gradientRadii
} else if (isFirst) {
if (tabLayout.isHorizontal()) {
if (tabLayout.isLayoutRtl) {
gradientRadii = floatArrayOf(
0f,
0f,
this@DslTabBorder.gradientRadii[2],
this@DslTabBorder.gradientRadii[3],
this@DslTabBorder.gradientRadii[4],
this@DslTabBorder.gradientRadii[5],
0f,
0f
)
} else {
gradientRadii = floatArrayOf(
this@DslTabBorder.gradientRadii[0],
this@DslTabBorder.gradientRadii[1],
0f,
0f,
0f,
0f,
this@DslTabBorder.gradientRadii[6],
this@DslTabBorder.gradientRadii[7]
)
}
} else {
gradientRadii = floatArrayOf(
this@DslTabBorder.gradientRadii[0],
this@DslTabBorder.gradientRadii[1],
this@DslTabBorder.gradientRadii[2],
this@DslTabBorder.gradientRadii[3],
0f,
0f,
0f,
0f
)
}
} else if (isLast) {
if (tabLayout.isHorizontal()) {
if (tabLayout.isLayoutRtl) {
gradientRadii = floatArrayOf(
this@DslTabBorder.gradientRadii[0],
this@DslTabBorder.gradientRadii[1],
0f,
0f,
0f,
0f,
this@DslTabBorder.gradientRadii[6],
this@DslTabBorder.gradientRadii[7]
)
} else {
gradientRadii = floatArrayOf(
0f,
0f,
this@DslTabBorder.gradientRadii[2],
this@DslTabBorder.gradientRadii[3],
this@DslTabBorder.gradientRadii[4],
this@DslTabBorder.gradientRadii[5],
0f,
0f
)
}
} else {
gradientRadii = floatArrayOf(
0f,
0f,
0f,
0f,
this@DslTabBorder.gradientRadii[4],
this@DslTabBorder.gradientRadii[5],
this@DslTabBorder.gradientRadii[6],
this@DslTabBorder.gradientRadii[7]
)
}
}
}
itemSelectBgDrawable = drawable
ViewCompat.setBackground(itemView, itemSelectBgDrawable)
} else {
ViewCompat.setBackground(itemView, itemDeselectBgDrawable)
}
}
}

View File

@@ -1,153 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.widget.LinearLayout
/**
* 垂直分割线
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/27
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabDivider : DslGradientDrawable() {
var dividerWidth = 2 * dpi
var dividerHeight = 2 * dpi
var dividerMarginLeft = 0
var dividerMarginRight = 0
var dividerMarginTop = 0
var dividerMarginBottom = 0
/**
* [LinearLayout.SHOW_DIVIDER_BEGINNING]
* [LinearLayout.SHOW_DIVIDER_MIDDLE]
* [LinearLayout.SHOW_DIVIDER_END]
* */
var dividerShowMode = LinearLayout.SHOW_DIVIDER_MIDDLE
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
super.initAttribute(context, attributeSet)
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
dividerWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_width,
dividerWidth
)
dividerHeight = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_height,
dividerHeight
)
dividerMarginLeft = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_left,
dividerMarginLeft
)
dividerMarginRight = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_right,
dividerMarginRight
)
dividerMarginTop = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_top,
dividerMarginTop
)
dividerMarginBottom = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_bottom,
dividerMarginBottom
)
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_divider_solid_color)) {
gradientSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_divider_solid_color,
gradientSolidColor
)
} else if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_stroke_color)) {
gradientSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_stroke_color,
gradientSolidColor
)
} else {
gradientSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_deselect_color,
gradientSolidColor
)
}
gradientStrokeColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_divider_stroke_color,
gradientStrokeColor
)
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_stroke_width,
0
)
val radiusSize =
typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_radius_size,
2 * dpi
)
cornerRadius(radiusSize.toFloat())
originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_divider_drawable)
dividerShowMode =
typedArray.getInt(R.styleable.DslTabLayout_tab_divider_show_mode, dividerShowMode)
typedArray.recycle()
if (originDrawable == null) {
//无自定义的drawable, 那么自绘.
updateOriginDrawable()
}
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
originDrawable?.apply {
bounds = this@DslTabDivider.bounds
draw(canvas)
}
}
val _tabLayout: DslTabLayout?
get() = if (callback is DslTabLayout) callback as DslTabLayout else null
/**
* [childIndex]位置前面是否需要分割线
* */
open fun haveBeforeDivider(childIndex: Int, childCount: Int): Boolean {
val tabLayout = _tabLayout
if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
if (childIndex == 0) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
}
return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
}
if (childIndex == 0) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
}
return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
}
/**
* [childIndex]位置后面是否需要分割线
* */
open fun haveAfterDivider(childIndex: Int, childCount: Int): Boolean {
val tabLayout = _tabLayout
if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
if (childIndex == childCount - 1) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
}
}
if (childIndex == childCount - 1) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
}
return false
}
}

View File

@@ -1,118 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.ViewGroup
/**
*
* Email:angcyo@126.com
* @author angcyo
* @date 2021/05/19
* Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
*/
open class DslTabHighlight(val tabLayout: DslTabLayout) : DslGradientDrawable() {
/**需要绘制的Drawable*/
var highlightDrawable: Drawable? = null
/**宽度测量模式*/
var highlightWidth = ViewGroup.LayoutParams.MATCH_PARENT
/**高度测量模式*/
var highlightHeight = ViewGroup.LayoutParams.MATCH_PARENT
/**宽度补偿*/
var highlightWidthOffset = 0
/**高度补偿*/
var highlightHeightOffset = 0
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
//super.initAttribute(context, attributeSet)
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
highlightDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_highlight_drawable)
highlightWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_highlight_width,
highlightWidth
)
highlightHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_highlight_height,
highlightHeight
)
highlightWidthOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_highlight_width_offset,
highlightWidthOffset
)
highlightHeightOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_highlight_height_offset,
highlightHeightOffset
)
typedArray.recycle()
if (highlightDrawable == null && isValidConfig()) {
updateOriginDrawable()
}
}
override fun updateOriginDrawable(): GradientDrawable? {
val drawable = super.updateOriginDrawable()
highlightDrawable = originDrawable
return drawable
}
override fun draw(canvas: Canvas) {
//super.draw(canvas)
val itemView = tabLayout.currentItemView
if (itemView != null) {
val lp = itemView.layoutParams
if (lp is DslTabLayout.LayoutParams) {
lp.highlightDrawable ?: highlightDrawable
} else {
highlightDrawable
}?.apply {
val drawWidth: Int = when (highlightWidth) {
ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredWidth
ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicWidth
else -> highlightWidth
} + highlightWidthOffset
val drawHeight: Int = when (highlightHeight) {
ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredHeight
ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicHeight
else -> highlightHeight
} + highlightHeightOffset
val centerX: Int = itemView.left + (itemView.right - itemView.left) / 2
val centerY: Int = itemView.top + (itemView.bottom - itemView.top) / 2
setBounds(
centerX - drawWidth / 2,
centerY - drawHeight / 2,
centerX + drawWidth / 2,
centerY + drawHeight / 2
)
draw(canvas)
canvas.save()
if (tabLayout.isHorizontal()) {
canvas.translate(itemView.left.toFloat(), 0f)
} else {
canvas.translate(0f, itemView.top.toFloat())
}
itemView.draw(canvas)
canvas.restore()
}
}
}
}

View File

@@ -1,931 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.withSave
import java.util.*
import kotlin.math.absoluteValue
import kotlin.math.max
/**
* 指示器
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/25
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabIndicator(val tabLayout: DslTabLayout) : DslGradientDrawable() {
companion object {
/**非颜色值*/
const val NO_COLOR = -2
//---style---
/**不绘制指示器*/
const val INDICATOR_STYLE_NONE = 0
/**指示器绘制在[itemView]的顶部*/
const val INDICATOR_STYLE_TOP = 0x1
/**指示器绘制在[itemView]的底部*/
const val INDICATOR_STYLE_BOTTOM = 0x2
/**默认样式,指示器绘制在[itemView]的中心*/
const val INDICATOR_STYLE_CENTER = 0x4
/**前景绘制,
* 默认是背景绘制, 指示器绘制[itemView]的背部, [itemView] 请不要设置background, 否则可能看不见*/
const val INDICATOR_STYLE_FOREGROUND = 0x1000
//---gravity---
/**指示器重力在开始的位置(横向左边, 纵向上边)*/
const val INDICATOR_GRAVITY_START = 0x1
/**指示器重力在结束的位置(横向右边, 纵向下边)*/
const val INDICATOR_GRAVITY_END = 0x2
/**指示器重力在中间*/
const val INDICATOR_GRAVITY_CENTER = 0x4
}
/**指示器绘制的样式*/
var indicatorStyle = INDICATOR_STYLE_NONE //初始化
/**[indicatorStyle]*/
val _indicatorDrawStyle: Int
get() = indicatorStyle.remove(INDICATOR_STYLE_FOREGROUND)
/**优先将指示器显示在[DslTabLayout]的什么位置
* [INDICATOR_GRAVITY_START] 开始的位置
* [INDICATOR_GRAVITY_END] 结束的位置
* [INDICATOR_GRAVITY_CENTER] 中间的位置*/
var indicatorGravity = INDICATOR_GRAVITY_CENTER
/**
* 指示器在流向下一个位置时, 是否采用[Flow]流线的方式改变宽度
* */
var indicatorEnableFlow: Boolean = false
/**指示器闪现效果, 从当前位置直接跨越到目标位置*/
var indicatorEnableFlash: Boolean = false
/**使用clip的方式绘制闪现效果*/
var indicatorEnableFlashClip: Boolean = true
/**当目标和当前的索引差值<=此值时, [Flow]效果才有效*/
var indicatorFlowStep: Int = 1
/**指示器绘制实体*/
var indicatorDrawable: Drawable? = null
set(value) {
field = tintDrawableColor(value, indicatorColor)
}
/**过滤[indicatorDrawable]的颜色*/
var indicatorColor: Int = NO_COLOR
set(value) {
field = value
indicatorDrawable = indicatorDrawable
}
/**
* 指示器的宽度
* WRAP_CONTENT: [childView]内容的宽度,
* MATCH_PARENT: [childView]的宽度
* 40dp: 固定值
* */
var indicatorWidth = 0 //初始化
/**宽度补偿*/
var indicatorWidthOffset = 0
/**
* 指示器的高度
* WRAP_CONTENT: [childView]内容的高度,
* MATCH_PARENT: [childView]的高度
* 40dp: 固定值
* */
var indicatorHeight = 0 //初始化
/**高度补偿*/
var indicatorHeightOffset = 0
/**XY轴方向补偿*/
var indicatorXOffset = 0
/**会根据[indicatorStyle]自动取负值*/
var indicatorYOffset = 0
/**
* 宽高[WRAP_CONTENT]时, 内容view的定位索引
* */
var indicatorContentIndex = -1
var indicatorContentId = View.NO_ID
/**切换时是否需要动画的支持*/
var indicatorAnim = true
/**在获取锚点view的宽高时, 是否需要忽略对应的padding属性*/
var ignoreChildPadding: Boolean = true
init {
callback = tabLayout
}
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
indicatorDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_indicator_drawable)
indicatorColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_indicator_color, indicatorColor)
indicatorStyle = typedArray.getInt(
R.styleable.DslTabLayout_tab_indicator_style,
if (tabLayout.isHorizontal()) INDICATOR_STYLE_BOTTOM else INDICATOR_STYLE_TOP
)
indicatorGravity = typedArray.getInt(
R.styleable.DslTabLayout_tab_indicator_gravity,
indicatorGravity
)
//初始化指示器的高度和宽度
if (indicatorStyle.have(INDICATOR_STYLE_FOREGROUND)) {
//前景绘制
indicatorWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_width,
if (tabLayout.isHorizontal()) ViewGroup.LayoutParams.MATCH_PARENT else 3 * dpi
)
indicatorHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_height,
if (tabLayout.isHorizontal()) 3 * dpi else ViewGroup.LayoutParams.MATCH_PARENT
)
indicatorXOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_x_offset,
if (tabLayout.isHorizontal()) 0 else 2 * dpi
)
indicatorYOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_y_offset,
if (tabLayout.isHorizontal()) 2 * dpi else 0
)
} else {
//背景绘制样式
if (tabLayout.isHorizontal()) {
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
} else {
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
}
indicatorWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_width,
indicatorWidth
)
indicatorHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_height,
indicatorHeight
)
indicatorXOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_x_offset,
indicatorXOffset
)
indicatorYOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_y_offset,
indicatorYOffset
)
}
ignoreChildPadding = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_ignore_child_padding,
!indicatorStyle.have(INDICATOR_STYLE_CENTER)
)
indicatorFlowStep =
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_flow_step, indicatorFlowStep)
indicatorEnableFlow = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_enable_flow,
indicatorEnableFlow
)
indicatorEnableFlash = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_enable_flash,
indicatorEnableFlash
)
indicatorEnableFlashClip = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_enable_flash_clip,
indicatorEnableFlashClip
)
indicatorWidthOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_width_offset,
indicatorWidthOffset
)
indicatorHeightOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_height_offset,
indicatorHeightOffset
)
indicatorContentIndex = typedArray.getInt(
R.styleable.DslTabLayout_tab_indicator_content_index,
indicatorContentIndex
)
indicatorContentId = typedArray.getResourceId(
R.styleable.DslTabLayout_tab_indicator_content_id,
indicatorContentId
)
indicatorAnim = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_anim,
indicatorAnim
)
//代码构建Drawable
gradientShape =
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_shape, gradientShape)
gradientSolidColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_solid_color,
gradientSolidColor
)
gradientStrokeColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_stroke_color,
gradientStrokeColor
)
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_stroke_width,
gradientStrokeWidth
)
gradientDashWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_dash_width,
gradientDashWidth.toInt()
).toFloat()
gradientDashGap = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_dash_gap,
gradientDashGap.toInt()
).toFloat()
val gradientRadius =
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_indicator_radius, 0)
if (gradientRadius > 0) {
Arrays.fill(gradientRadii, gradientRadius.toFloat())
} else {
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_radii)?.let {
_fillRadii(gradientRadii, it)
}
}
val gradientColors =
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_gradient_colors)
this.gradientColors = if (gradientColors.isNullOrEmpty()) {
val startColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_gradient_start_color,
Color.TRANSPARENT
)
val endColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_gradient_end_color,
Color.TRANSPARENT
)
if (startColor != endColor) {
intArrayOf(startColor, endColor)
} else {
this.gradientColors
}
} else {
_fillColor(gradientColors) ?: this.gradientColors
}
//...end
typedArray.recycle()
if (indicatorDrawable == null && isValidConfig()) {
updateOriginDrawable()
}
}
override fun updateOriginDrawable(): GradientDrawable? {
val drawable = super.updateOriginDrawable()
indicatorDrawable = originDrawable
return drawable
}
open fun tintDrawableColor(drawable: Drawable?, color: Int): Drawable? {
if (drawable == null || color == NO_COLOR) {
return drawable
}
return drawable.tintDrawableColor(color)
}
/**指示器需要参考的目标控件*/
open fun indicatorContentView(childView: View): View? {
val lp = childView.layoutParams as DslTabLayout.LayoutParams
val contentId =
if (lp.indicatorContentId != View.NO_ID) lp.indicatorContentId else indicatorContentId
if (contentId != View.NO_ID) {
return childView.findViewById(contentId)
}
//如果child强制指定了index, 就用指定的.
val contentIndex =
if (lp.indicatorContentIndex >= 0) lp.indicatorContentIndex else indicatorContentIndex
return if (contentIndex >= 0 && childView is ViewGroup && contentIndex in 0 until childView.childCount) {
//有指定
val contentChildView = childView.getChildAt(contentIndex)
contentChildView
} else {
//没有指定
null
}
}
/**根据指定[index]索引, 获取目标[View]*/
open fun targetChildView(
index: Int,
onChildView: (childView: View, contentChildView: View?) -> Unit
) {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
onChildView(childView, indicatorContentView(childView))
}
}
open fun getChildTargetPaddingLeft(childView: View): Int =
if (ignoreChildPadding) childView.paddingLeft else 0
open fun getChildTargetPaddingRight(childView: View): Int =
if (ignoreChildPadding) childView.paddingRight else 0
open fun getChildTargetPaddingTop(childView: View): Int =
if (ignoreChildPadding) childView.paddingTop else 0
open fun getChildTargetPaddingBottom(childView: View): Int =
if (ignoreChildPadding) childView.paddingBottom else 0
open fun getChildTargetWidth(childView: View): Int =
if (ignoreChildPadding) childView.viewDrawWidth else childView.measuredWidth
open fun getChildTargetHeight(childView: View): Int =
if (ignoreChildPadding) childView.viewDrawHeight else childView.measuredHeight
/**
* [childview]对应的中心x坐标
* */
open fun getChildTargetX(index: Int, gravity: Int = indicatorGravity): Int {
var result = if (index > 0) tabLayout.maxWidth else 0
targetChildView(index) { childView, contentChildView ->
result = if (contentChildView == null) {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.left
INDICATOR_GRAVITY_END -> childView.right
else -> childView.left + getChildTargetPaddingLeft(childView) + getChildTargetWidth(
childView
) / 2
}
} else {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.left + contentChildView.left
INDICATOR_GRAVITY_END -> childView.left + contentChildView.right
else -> childView.left + contentChildView.left + getChildTargetPaddingLeft(
contentChildView
) + getChildTargetWidth(
contentChildView
) / 2
}
}
}
return result
}
open fun getChildTargetY(index: Int, gravity: Int = indicatorGravity): Int {
var result = if (index > 0) tabLayout.maxHeight else 0
targetChildView(index) { childView, contentChildView ->
result = if (contentChildView == null) {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.top
INDICATOR_GRAVITY_END -> childView.bottom
else -> childView.top + getChildTargetPaddingTop(childView) + getChildTargetHeight(
childView
) / 2
}
} else {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.top + contentChildView.top
INDICATOR_GRAVITY_END -> childView.top + childView.bottom
else -> childView.top + contentChildView.top + getChildTargetPaddingTop(
contentChildView
) + getChildTargetHeight(
contentChildView
) / 2
}
}
}
return result
}
open fun getIndicatorDrawWidth(index: Int): Int {
var result = indicatorWidth
when (indicatorWidth) {
ViewGroup.LayoutParams.WRAP_CONTENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = getChildTargetWidth(indicatorContentView(childView) ?: childView)
}
}
ViewGroup.LayoutParams.MATCH_PARENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = childView.measuredWidth
}
}
}
return result + indicatorWidthOffset
}
open fun getIndicatorDrawHeight(index: Int): Int {
var result = indicatorHeight
when (indicatorHeight) {
ViewGroup.LayoutParams.WRAP_CONTENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = getChildTargetHeight(indicatorContentView(childView) ?: childView)
}
}
ViewGroup.LayoutParams.MATCH_PARENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = childView.measuredHeight
}
}
}
return result + indicatorHeightOffset
}
override fun draw(canvas: Canvas) {
//super.draw(canvas)
if (!isVisible || _indicatorDrawStyle == INDICATOR_STYLE_NONE || indicatorDrawable == null) {
//不绘制
return
}
if (tabLayout.isHorizontal()) {
drawHorizontal(canvas)
} else {
drawVertical(canvas)
}
}
fun drawHorizontal(canvas: Canvas) {
val childSize = tabLayout.dslSelector.visibleViewList.size
var currentIndex = currentIndex
if (_targetIndex in 0 until childSize) {
currentIndex = max(0, currentIndex)
}
if (currentIndex in 0 until childSize) {
} else {
//无效的index
return
}
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
val drawTargetX = getChildTargetX(currentIndex)
val drawWidth = getIndicatorDrawWidth(currentIndex)
val drawHeight = getIndicatorDrawHeight(currentIndex)
val drawLeft = drawTargetX - drawWidth / 2 + indicatorXOffset
//动画过程中的left
var animLeft = drawLeft
//width
var animWidth = drawWidth
//动画执行过程中, 高度额外变大的值
var animExHeight = 0
//end value
val nextDrawTargetX = getChildTargetX(_targetIndex)
val nextDrawWidth = getIndicatorDrawWidth(_targetIndex)
val nextDrawLeft = nextDrawTargetX - nextDrawWidth / 2 + indicatorXOffset
var animEndWidth = nextDrawWidth
var animEndLeft = nextDrawLeft
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
//动画过程参数计算变量
val animStartLeft = drawLeft
val animStartWidth = drawWidth
val animEndHeight = getIndicatorDrawHeight(_targetIndex)
if (indicatorEnableFlash) {
//闪现效果
animWidth = (animWidth * (1 - positionOffset)).toInt()
animEndWidth = (animEndWidth * positionOffset).toInt()
animLeft = drawTargetX - animWidth / 2 + indicatorXOffset
animEndLeft = nextDrawLeft
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
//激活了流动效果
val flowEndWidth: Int
if (_targetIndex > currentIndex) {
flowEndWidth = animEndLeft - animStartLeft + animEndWidth
//目标在右边
animLeft = if (positionOffset >= 0.5) {
(animStartLeft + (animEndLeft - animStartLeft) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
animStartLeft
}
} else {
flowEndWidth = animStartLeft - animEndLeft + animStartWidth
//目标在左边
animLeft = if (positionOffset >= 0.5) {
animEndLeft
} else {
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset / 0.5f).toInt()
}
}
animWidth = if (positionOffset >= 0.5) {
(flowEndWidth - (flowEndWidth - animEndWidth) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
(animStartWidth + (flowEndWidth - animStartWidth) * positionOffset / 0.5f).toInt()
}
} else {
//默认平移效果
if (_targetIndex > currentIndex) {
//目标在右边
animLeft =
(animStartLeft + (animEndLeft - animStartLeft) * positionOffset).toInt()
} else {
//目标在左边
animLeft =
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset).toInt()
}
//动画过程中的宽度
animWidth =
(animStartWidth + (animEndWidth - animStartWidth) * positionOffset).toInt()
}
animExHeight = ((animEndHeight - drawHeight) * positionOffset).toInt()
}
//前景
val drawTop = when (_indicatorDrawStyle) {
//底部绘制
INDICATOR_STYLE_BOTTOM -> viewHeight - drawHeight - indicatorYOffset
//顶部绘制
INDICATOR_STYLE_TOP -> 0 + indicatorYOffset
//居中绘制
else -> paddingTop + viewDrawHeight / 2 - drawHeight / 2 + indicatorYOffset -
animExHeight +
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
}
indicatorDrawable?.apply {
if (indicatorEnableFlash) {
//flash
if (indicatorEnableFlashClip) {
drawIndicatorClipHorizontal(
this,
canvas,
drawLeft,
drawTop,
drawLeft + drawWidth,
drawTop + drawHeight + animExHeight,
animWidth,
1 - positionOffset
)
} else {
drawIndicator(
this, canvas, animLeft,
drawTop,
animLeft + animWidth,
drawTop + drawHeight + animExHeight,
1 - positionOffset
)
}
if (_targetIndex in 0 until childSize) {
if (indicatorEnableFlashClip) {
drawIndicatorClipHorizontal(
this,
canvas,
nextDrawLeft,
drawTop,
nextDrawLeft + nextDrawWidth,
drawTop + drawHeight + animExHeight,
animEndWidth,
positionOffset
)
} else {
drawIndicator(
this, canvas, animEndLeft,
drawTop,
animEndLeft + animEndWidth,
drawTop + drawHeight + animExHeight,
positionOffset
)
}
}
} else {
//normal
drawIndicator(
this, canvas, animLeft,
drawTop,
animLeft + animWidth,
drawTop + drawHeight + animExHeight,
1 - positionOffset
)
}
}
}
fun drawIndicator(
indicator: Drawable,
canvas: Canvas,
l: Int,
t: Int,
r: Int,
b: Int,
offset: Float
) {
indicator.apply {
if (this is ITabIndicatorDraw) {
setBounds(l, t, r, b)
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
} else {
val width = r - l
val height = b - t
setBounds(0, 0, width, height)
canvas.withSave {
translate(l.toFloat(), t.toFloat())
draw(canvas)
}
}
}
}
fun drawIndicatorClipHorizontal(
indicator: Drawable,
canvas: Canvas,
l: Int,
t: Int,
r: Int,
b: Int,
endWidth: Int,
offset: Float
) {
indicator.apply {
canvas.save()
val dx = (r - l - endWidth) / 2
canvas.clipRect(l + dx, t, r - dx, b)
setBounds(l, t, r, b)
if (this is ITabIndicatorDraw) {
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
} else {
draw(canvas)
}
canvas.restore()
}
}
fun drawIndicatorClipVertical(
indicator: Drawable,
canvas: Canvas,
l: Int,
t: Int,
r: Int,
b: Int,
endHeight: Int,
offset: Float
) {
indicator.apply {
canvas.save()
val dy = (b - t - endHeight) / 2
canvas.clipRect(l, t + dy, r, b - dy)
setBounds(l, t, r, b)
if (this is ITabIndicatorDraw) {
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
} else {
draw(canvas)
}
canvas.restore()
}
}
fun drawVertical(canvas: Canvas) {
val childSize = tabLayout.dslSelector.visibleViewList.size
var currentIndex = currentIndex
if (_targetIndex in 0 until childSize) {
currentIndex = max(0, currentIndex)
}
if (currentIndex in 0 until childSize) {
} else {
//无效的index
return
}
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
val drawTargetY = getChildTargetY(currentIndex)
val drawWidth = getIndicatorDrawWidth(currentIndex)
val drawHeight = getIndicatorDrawHeight(currentIndex)
val drawTop = drawTargetY - drawHeight / 2 + indicatorYOffset
//动画过程中的top
var animTop = drawTop
//height
var animHeight = drawHeight
//动画执行过程中, 宽度额外变大的值
var animExWidth = 0
//end value
val nextDrawTargetY = getChildTargetY(_targetIndex)
val nextDrawHeight = getIndicatorDrawHeight(_targetIndex)
val nextDrawTop = nextDrawTargetY - nextDrawHeight / 2 + indicatorYOffset
var animEndHeight = nextDrawHeight
var animEndTop = nextDrawTop
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
//动画过程参数计算变量
val animStartTop = drawTop
val animStartHeight = drawHeight
val animEndWidth = getIndicatorDrawWidth(_targetIndex)
if (indicatorEnableFlash) {
//闪现效果
animHeight = (animHeight * (1 - positionOffset)).toInt()
animEndHeight = (animEndHeight * positionOffset).toInt()
animTop = drawTargetY - animHeight / 2 + indicatorXOffset
animEndTop = nextDrawTargetY - animEndHeight / 2 + indicatorXOffset
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
//激活了流动效果
val flowEndHeight: Int
if (_targetIndex > currentIndex) {
flowEndHeight = animEndTop - animStartTop + animEndHeight
//目标在下边
animTop = if (positionOffset >= 0.5) {
(animStartTop + (animEndTop - animStartTop) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
animStartTop
}
} else {
flowEndHeight = animStartTop - animEndTop + animStartHeight
//目标在上边
animTop = if (positionOffset >= 0.5) {
animEndTop
} else {
(animStartTop - (animStartTop - animEndTop) * positionOffset / 0.5f).toInt()
}
}
animHeight = if (positionOffset >= 0.5) {
(flowEndHeight - (flowEndHeight - animEndHeight) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
(animStartHeight + (flowEndHeight - animStartHeight) * positionOffset / 0.5f).toInt()
}
} else {
if (_targetIndex > currentIndex) {
//目标在下边
animTop = (animStartTop + (animEndTop - animStartTop) * positionOffset).toInt()
} else {
//目标在上边
animTop = (animStartTop - (animStartTop - animEndTop) * positionOffset).toInt()
}
//动画过程中的宽度
animHeight =
(animStartHeight + (animEndHeight - animStartHeight) * positionOffset).toInt()
}
animExWidth = ((animEndWidth - drawWidth) * positionOffset).toInt()
}
val drawLeft = when (_indicatorDrawStyle) {
INDICATOR_STYLE_BOTTOM -> {
//右边/底部绘制
viewWidth - drawWidth - indicatorXOffset
}
INDICATOR_STYLE_TOP -> {
//左边/顶部绘制
0 + indicatorXOffset
}
else -> {
//居中绘制
paddingLeft + indicatorXOffset + (viewDrawWidth / 2 - drawWidth / 2) -
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
}
}
indicatorDrawable?.apply {
//flash
if (indicatorEnableFlash) {
if (indicatorEnableFlashClip) {
drawIndicatorClipVertical(
this, canvas, drawLeft,
drawTop,
drawLeft + drawWidth + animExWidth,
drawTop + drawHeight,
animHeight,
1 - positionOffset
)
} else {
drawIndicator(
this, canvas, drawLeft,
animTop,
drawLeft + drawWidth + animExWidth,
animTop + animHeight,
1 - positionOffset
)
}
if (_targetIndex in 0 until childSize) {
if (indicatorEnableFlashClip) {
drawIndicatorClipVertical(
this, canvas, drawLeft,
nextDrawTop,
drawLeft + drawWidth + animExWidth,
nextDrawTop + nextDrawHeight,
animEndHeight,
positionOffset
)
} else {
drawIndicator(
this, canvas, drawLeft,
animEndTop,
drawLeft + drawWidth + animExWidth,
animEndTop + animEndHeight,
positionOffset
)
}
}
} else {
drawIndicator(
this, canvas, drawLeft,
animTop,
drawLeft + drawWidth + animExWidth,
animTop + animHeight,
1 - positionOffset
)
}
}
}
fun _childConvexHeight(index: Int): Int {
if (attachView is ViewGroup) {
((attachView as ViewGroup).getChildAt(index).layoutParams as? DslTabLayout.LayoutParams)?.apply {
return layoutConvexHeight
}
}
return 0
}
/**
* 距离[_targetIndex]的偏移比例.[0->1]的过程
* */
var positionOffset: Float = 0f
set(value) {
field = value
invalidateSelf()
}
/**当前绘制的index*/
var currentIndex: Int = -1
/**滚动目标的index*/
var _targetIndex = -1
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,526 +0,0 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.IdRes
import com.angcyo.tablayout.DslTabIndicator.Companion.NO_COLOR
import kotlin.math.max
import kotlin.math.min
/**
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/26
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabLayoutConfig(val tabLayout: DslTabLayout) : DslSelectorConfig() {
/**是否开启文本颜色*/
var tabEnableTextColor = true
set(value) {
field = value
if (field) {
tabEnableIcoColor = true
}
}
/**是否开启颜色渐变效果*/
var tabEnableGradientColor = false
set(value) {
field = value
if (field) {
tabEnableIcoGradientColor = true
}
}
/**是否激活指示器的颜色渐变效果*/
var tabEnableIndicatorGradientColor = false
/**选中的文本颜色*/
var tabSelectColor: Int = Color.WHITE //Color.parseColor("#333333")
/**未选中的文本颜色*/
var tabDeselectColor: Int = Color.parseColor("#999999")
/**是否开启Bold, 文本加粗*/
var tabEnableTextBold = false
/**是否使用粗体字体的方式设置粗体, 否则使用[Paint.FAKE_BOLD_TEXT_FLAG]
* 需要先激活[tabEnableTextBold]*/
var tabUseTypefaceBold = false
/**是否开启图标颜色*/
var tabEnableIcoColor = true
/**是否开启图标颜色渐变效果*/
var tabEnableIcoGradientColor = false
/**选中的图标颜色*/
var tabIcoSelectColor: Int = NO_COLOR
get() {
return if (field == NO_COLOR) tabSelectColor else field
}
/**未选中的图标颜色*/
var tabIcoDeselectColor: Int = NO_COLOR
get() {
return if (field == NO_COLOR) tabDeselectColor else field
}
/**是否开启scale渐变效果*/
var tabEnableGradientScale = false
/**最小缩放的比例*/
var tabMinScale = 0.8f
/**最大缩放的比例*/
var tabMaxScale = 1.2f
/**是否开启字体大小渐变效果*/
var tabEnableGradientTextSize = true
/**tab中文本字体未选中时的字体大小, >0时激活*/
var tabTextMinSize = -1f
/**tab中文本字体选中时的字体大小, >0时激活*/
var tabTextMaxSize = -1f
/**渐变效果实现的回调*/
var tabGradientCallback = TabGradientCallback()
/**指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上.
* 如果指定的控件不存在, 控件会降权至[ItemView]*/
@IdRes
var tabTextViewId: Int = View.NO_ID
/**指定图标控件的id*/
@IdRes
var tabIconViewId: Int = View.NO_ID
/**返回用于配置文本样式的控件*/
var onGetTextStyleView: (itemView: View, index: Int) -> TextView? = { itemView, _ ->
if (tabTextViewId == View.NO_ID) {
var tv: TextView? = if (itemView is TextView) itemView else null
if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
if (it is TextView) {
tv = it
}
}
}
if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(tabLayout.tabIndicator.indicatorContentId)?.let {
if (it is TextView) {
tv = it
}
}
}
val lp = itemView.layoutParams
if (lp is DslTabLayout.LayoutParams) {
if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
itemView.getChildOrNull(lp.indicatorContentIndex)?.let {
if (it is TextView) {
tv = it
}
}
}
if (lp.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(lp.indicatorContentId)?.let {
if (it is TextView) {
tv = it
}
}
}
if (lp.contentTextViewIndex != -1 && itemView is ViewGroup) {
itemView.getChildOrNull(lp.contentTextViewIndex)?.let {
if (it is TextView) {
tv = it
}
}
}
if (lp.contentTextViewId != View.NO_ID) {
itemView.findViewById<View>(lp.contentTextViewId)?.let {
if (it is TextView) {
tv = it
}
}
}
}
tv
} else {
itemView.findViewById(tabTextViewId)
}
}
/**返回用于配置ico样式的控件*/
var onGetIcoStyleView: (itemView: View, index: Int) -> View? = { itemView, _ ->
if (tabIconViewId == View.NO_ID) {
var iv: View? = itemView
if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
iv = it
}
}
if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(tabLayout.tabIndicator.indicatorContentId)?.let {
iv = it
}
}
val lp = itemView.layoutParams
if (lp is DslTabLayout.LayoutParams) {
if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
iv = itemView.getChildOrNull(lp.indicatorContentIndex)
}
if (lp.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(lp.indicatorContentId)?.let {
iv = it
}
}
if (lp.contentIconViewIndex != -1 && itemView is ViewGroup) {
iv = itemView.getChildOrNull(lp.contentIconViewIndex)
}
if (lp.contentIconViewId != View.NO_ID) {
itemView.findViewById<View>(lp.contentIconViewId)?.let {
iv = it
}
}
}
iv
} else {
itemView.findViewById(tabIconViewId)
}
}
/**获取渐变结束时,指示器的颜色.*/
var onGetGradientIndicatorColor: (fromIndex: Int, toIndex: Int, positionOffset: Float) -> Int =
{ fromIndex, toIndex, positionOffset ->
tabLayout.tabIndicator.indicatorColor
}
init {
onStyleItemView = { itemView, index, select ->
onUpdateItemStyle(itemView, index, select)
}
onSelectIndexChange = { fromIndex, selectIndexList, reselect, fromUser ->
val toIndex = selectIndexList.last()
tabLayout._viewPagerDelegate?.onSetCurrentItem(fromIndex, toIndex, reselect, fromUser)
}
}
/**xml属性读取*/
open fun initAttribute(context: Context, attributeSet: AttributeSet? = null) {
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
tabSelectColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_select_color, tabSelectColor)
tabDeselectColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_deselect_color,
tabDeselectColor
)
tabIcoSelectColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_ico_select_color, NO_COLOR)
tabIcoDeselectColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_ico_deselect_color, NO_COLOR)
tabEnableTextColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_text_color,
tabEnableTextColor
)
tabEnableIndicatorGradientColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_indicator_gradient_color,
tabEnableIndicatorGradientColor
)
tabEnableGradientColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_gradient_color,
tabEnableGradientColor
)
tabEnableIcoColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_ico_color,
tabEnableIcoColor
)
tabEnableIcoGradientColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_ico_gradient_color,
tabEnableIcoGradientColor
)
tabEnableTextBold = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_text_bold,
tabEnableTextBold
)
tabUseTypefaceBold = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_use_typeface_bold,
tabUseTypefaceBold
)
tabEnableGradientScale = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_gradient_scale,
tabEnableGradientScale
)
tabMinScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_min_scale, tabMinScale)
tabMaxScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_max_scale, tabMaxScale)
tabEnableGradientTextSize = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_gradient_text_size,
tabEnableGradientTextSize
)
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_min_size)) {
tabTextMinSize = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_text_min_size,
tabTextMinSize.toInt()
).toFloat()
}
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_max_size)) {
tabTextMaxSize = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_text_max_size,
tabTextMaxSize.toInt()
).toFloat()
}
tabTextViewId =
typedArray.getResourceId(R.styleable.DslTabLayout_tab_text_view_id, tabTextViewId)
tabIconViewId =
typedArray.getResourceId(R.styleable.DslTabLayout_tab_icon_view_id, tabIconViewId)
typedArray.recycle()
}
/**更新item的样式*/
open fun onUpdateItemStyle(itemView: View, index: Int, select: Boolean) {
//"$itemView\n$index\n$select".logw()
(onGetTextStyleView(itemView, index))?.apply {
//文本加粗
paint?.apply {
if (tabEnableTextBold && select) {
//设置粗体
if (tabUseTypefaceBold) {
typeface = Typeface.defaultFromStyle(Typeface.BOLD)
} else {
flags = flags or Paint.FAKE_BOLD_TEXT_FLAG
isFakeBoldText = true
}
} else {
//取消粗体
if (tabUseTypefaceBold) {
typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
} else {
flags = flags and Paint.FAKE_BOLD_TEXT_FLAG.inv()
isFakeBoldText = false
}
}
}
if (tabEnableTextColor) {
//文本颜色
setTextColor(if (select) tabSelectColor else tabDeselectColor)
}
if (tabTextMaxSize > 0 || tabTextMinSize > 0) {
//文本字体大小
val minTextSize = min(tabTextMinSize, tabTextMaxSize)
val maxTextSize = max(tabTextMinSize, tabTextMaxSize)
setTextSize(
TypedValue.COMPLEX_UNIT_PX,
if (select) maxTextSize else minTextSize
)
}
}
if (tabEnableIcoColor) {
onGetIcoStyleView(itemView, index)?.apply {
_updateIcoColor(this, if (select) tabIcoSelectColor else tabIcoDeselectColor)
}
}
if (tabEnableGradientScale) {
itemView.scaleX = if (select) tabMaxScale else tabMinScale
itemView.scaleY = if (select) tabMaxScale else tabMinScale
}
if (tabLayout.drawBorder) {
tabLayout.tabBorder?.updateItemBackground(tabLayout, itemView, index, select)
}
}
/**
* [DslTabLayout]滚动时回调.
* */
open fun onPageIndexScrolled(fromIndex: Int, toIndex: Int, positionOffset: Float) {
}
/**
* [onPageIndexScrolled]
* */
open fun onPageViewScrolled(fromView: View?, toView: View, positionOffset: Float) {
//"$fromView\n$toView\n$positionOffset".logi()
if (fromView != toView) {
val fromIndex = tabLayout.tabIndicator.currentIndex
val toIndex = tabLayout.tabIndicator._targetIndex
if (tabEnableIndicatorGradientColor) {
val startColor = onGetGradientIndicatorColor(fromIndex, fromIndex, 0f)
val endColor = onGetGradientIndicatorColor(fromIndex, toIndex, positionOffset)
tabLayout.tabIndicator.indicatorColor =
evaluateColor(positionOffset, startColor, endColor)
}
if (tabEnableGradientColor) {
//文本渐变
fromView?.apply {
_gradientColor(
onGetTextStyleView(this, fromIndex),
tabSelectColor,
tabDeselectColor,
positionOffset
)
}
_gradientColor(
onGetTextStyleView(toView, toIndex),
tabDeselectColor,
tabSelectColor,
positionOffset
)
}
if (tabEnableIcoGradientColor) {
//图标渐变
fromView?.apply {
_gradientIcoColor(
onGetIcoStyleView(this, fromIndex),
tabIcoSelectColor,
tabIcoDeselectColor,
positionOffset
)
}
_gradientIcoColor(
onGetIcoStyleView(toView, toIndex),
tabIcoDeselectColor,
tabIcoSelectColor,
positionOffset
)
}
if (tabEnableGradientScale) {
//scale渐变
_gradientScale(fromView, tabMaxScale, tabMinScale, positionOffset)
_gradientScale(toView, tabMinScale, tabMaxScale, positionOffset)
}
if (tabEnableGradientTextSize &&
tabTextMaxSize > 0 &&
tabTextMinSize > 0 &&
tabTextMinSize != tabTextMaxSize
) {
//文本字体大小渐变
_gradientTextSize(
fromView?.run { onGetTextStyleView(this, fromIndex) },
tabTextMaxSize,
tabTextMinSize,
positionOffset
)
_gradientTextSize(
onGetTextStyleView(toView, toIndex),
tabTextMinSize,
tabTextMaxSize,
positionOffset
)
if (toIndex == tabLayout.dslSelector.visibleViewList.lastIndex || toIndex == 0) {
tabLayout._scrollToTarget(toIndex, false)
}
}
}
}
open fun _gradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
tabGradientCallback.onGradientColor(view, startColor, endColor, percent)
}
open fun _gradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
tabGradientCallback.onGradientIcoColor(view, startColor, endColor, percent)
}
open fun _gradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
tabGradientCallback.onGradientScale(view, startScale, endScale, percent)
}
open fun _gradientTextSize(
view: TextView?,
startTextSize: Float,
endTextSize: Float,
percent: Float
) {
tabGradientCallback.onGradientTextSize(view, startTextSize, endTextSize, percent)
}
open fun _updateIcoColor(view: View?, color: Int) {
tabGradientCallback.onUpdateIcoColor(view, color)
}
}
open class TabGradientCallback {
open fun onGradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
(view as? TextView)?.apply {
setTextColor(evaluateColor(percent, startColor, endColor))
}
}
open fun onGradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
onUpdateIcoColor(view, evaluateColor(percent, startColor, endColor))
}
open fun onUpdateIcoColor(view: View?, color: Int) {
view?.tintDrawableColor(color)
}
open fun onGradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
view?.apply {
(startScale + (endScale - startScale) * percent).let {
scaleX = it
scaleY = it
}
}
}
open fun onGradientTextSize(
view: TextView?,
startTextSize: Float,
endTextSize: Float,
percent: Float
) {
view?.apply {
setTextSize(
TypedValue.COMPLEX_UNIT_PX,
(startTextSize + (endTextSize - startTextSize) * percent)
)
}
}
}

View File

@@ -1,22 +0,0 @@
package com.angcyo.tablayout
import android.graphics.Canvas
/**
* 用来实现[DslTabIndicator]的自绘
* Email:angcyo@126.com
* @author angcyo
* @date 2022/02/21
* Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
*/
interface ITabIndicatorDraw {
/**绘制指示器
* [positionOffset] 页面偏移量*/
fun onDrawTabIndicator(
tabIndicator: DslTabIndicator,
canvas: Canvas,
positionOffset: Float
)
}

View File

@@ -1,334 +0,0 @@
package com.angcyo.tablayout
import android.app.Activity
import android.content.Context
import android.content.res.Resources
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.math.MathUtils
/**
*
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/23
*/
internal val dpi: Int
get() = dp.toInt()
internal val dp: Float
get() = Resources.getSystem().displayMetrics.density
internal val View.dpi: Int
get() = context.resources.displayMetrics.density.toInt()
internal val View.screenWidth: Int
get() = context.resources.displayMetrics.widthPixels
internal val View.screenHeight: Int
get() = context.resources.displayMetrics.heightPixels
internal val View.viewDrawWidth: Int
get() = measuredWidth - paddingLeft - paddingRight
internal val View.viewDrawHeight: Int
get() = measuredHeight - paddingTop - paddingBottom
/**Match_Parent*/
internal fun exactlyMeasure(size: Int): Int =
View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY)
internal fun exactlyMeasure(size: Float): Int = exactlyMeasure(size.toInt())
/**Wrap_Content*/
internal fun atmostMeasure(size: Int): Int =
View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.AT_MOST)
internal fun Int.have(value: Int): Boolean = if (this == 0 || value == 0) {
false
} else if (this == 0 && value == 0) {
true
} else {
((this > 0 && value > 0) || (this < 0 && value < 0)) && this and value == value
}
internal fun Int.remove(value: Int): Int = this and value.inv()
internal fun clamp(value: Float, min: Float, max: Float): Float {
if (value < min) {
return min
} else if (value > max) {
return max
}
return value
}
internal fun clamp(value: Int, min: Int, max: Int): Int {
if (value < min) {
return min
} else if (value > max) {
return max
}
return value
}
internal fun Any.logi() {
Log.i("DslTabLayout", "$this")
}
internal fun Any.logw() {
Log.w("DslTabLayout", "$this")
}
internal fun Any.loge() {
Log.e("DslTabLayout", "$this")
}
internal fun View.calcLayoutWidthHeight(
rLayoutWidth: String?, rLayoutHeight: String?,
parentWidth: Int, parentHeight: Int,
rLayoutWidthExclude: Int = 0, rLayoutHeightExclude: Int = 0
): IntArray {
val size = intArrayOf(-1, -1)
if (TextUtils.isEmpty(rLayoutWidth) && TextUtils.isEmpty(rLayoutHeight)) {
return size
}
if (!TextUtils.isEmpty(rLayoutWidth)) {
if (rLayoutWidth!!.contains("sw", true)) {
val ratio = rLayoutWidth.replace("sw", "", true).toFloatOrNull()
ratio?.let {
size[0] = (ratio * (screenWidth - rLayoutWidthExclude)).toInt()
}
} else if (rLayoutWidth!!.contains("pw", true)) {
val ratio = rLayoutWidth.replace("pw", "", true).toFloatOrNull()
ratio?.let {
size[0] = (ratio * (parentWidth - rLayoutWidthExclude)).toInt()
}
}
}
if (!TextUtils.isEmpty(rLayoutHeight)) {
if (rLayoutHeight!!.contains("sh", true)) {
val ratio = rLayoutHeight.replace("sh", "", true).toFloatOrNull()
ratio?.let {
size[1] = (ratio * (screenHeight - rLayoutHeightExclude)).toInt()
}
} else if (rLayoutHeight!!.contains("ph", true)) {
val ratio = rLayoutHeight.replace("ph", "", true).toFloatOrNull()
ratio?.let {
size[1] = (ratio * (parentHeight - rLayoutHeightExclude)).toInt()
}
}
}
return size
}
internal fun evaluateColor(fraction: Float /*0-1*/, startColor: Int, endColor: Int): Int {
val fr = MathUtils.clamp(fraction, 0f, 1f)
val startA = startColor shr 24 and 0xff
val startR = startColor shr 16 and 0xff
val startG = startColor shr 8 and 0xff
val startB = startColor and 0xff
val endA = endColor shr 24 and 0xff
val endR = endColor shr 16 and 0xff
val endG = endColor shr 8 and 0xff
val endB = endColor and 0xff
return startA + (fr * (endA - startA)).toInt() shl 24 or
(startR + (fr * (endR - startR)).toInt() shl 16) or
(startG + (fr * (endG - startG)).toInt() shl 8) or
startB + (fr * (endB - startB)).toInt()
}
internal fun Drawable?.tintDrawableColor(color: Int): Drawable? {
if (this == null) {
return this
}
val wrappedDrawable =
DrawableCompat.wrap(this).mutate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
DrawableCompat.setTint(wrappedDrawable, color)
} else {
wrappedDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
}
return wrappedDrawable
}
internal fun View?.tintDrawableColor(color: Int) {
when (this) {
is TextView -> {
val drawables = arrayOfNulls<Drawable?>(4)
compoundDrawables.forEachIndexed { index, drawable ->
drawables[index] = drawable?.tintDrawableColor(color)
}
setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
}
is ImageView -> {
setImageDrawable(drawable?.tintDrawableColor(color))
}
}
}
internal fun Paint?.textWidth(text: String?): Float {
if (TextUtils.isEmpty(text)) {
return 0f
}
return this?.run {
measureText(text)
} ?: 0f
}
internal fun Paint?.textHeight(): Float = this?.run { descent() - ascent() } ?: 0f
internal fun View.getChildOrNull(index: Int): View? {
return if (this is ViewGroup) {
return if (index in 0 until childCount) {
getChildAt(index)
} else {
null
}
} else {
this
}
}
/**获取[View]在指定[parent]中的矩形坐标*/
internal fun View.getLocationInParent(parentView: View? = null, result: Rect = Rect()): Rect {
val parent: View? = parentView ?: (parent as? View)
if (parent == null) {
getViewRect(result)
} else {
result.set(0, 0, 0, 0)
if (this != parent) {
fun doIt(view: View, parent: View, rect: Rect) {
val viewParent = view.parent
if (viewParent is View) {
rect.left += view.left
rect.top += view.top
if (viewParent != parent) {
doIt(viewParent, parent, rect)
}
}
}
doIt(this, parent, result)
}
result.right = result.left + this.measuredWidth
result.bottom = result.top + this.measuredHeight
}
return result
}
/**
* 获取View, 相对于手机屏幕的矩形
* */
internal fun View.getViewRect(result: Rect = Rect()): Rect {
var offsetX = 0
var offsetY = 0
//横屏, 并且显示了虚拟导航栏的时候. 需要左边偏移
//只计算一次
(context as? Activity)?.let {
it.window.decorView.getGlobalVisibleRect(result)
if (result.width() > result.height()) {
//横屏了
offsetX = navBarHeight(it)
}
}
return getViewRect(offsetX, offsetY, result)
}
/**
* 获取View, 相对于手机屏幕的矩形, 带皮阿尼一
* */
internal fun View.getViewRect(offsetX: Int, offsetY: Int, result: Rect = Rect()): Rect {
//可见位置的坐标, 超出屏幕的距离会被剃掉
//getGlobalVisibleRect(r)
val r2 = IntArray(2)
//val r3 = IntArray(2)
//相对于屏幕的坐标
getLocationOnScreen(r2)
//相对于窗口的坐标
//getLocationInWindow(r3)
val left = r2[0] + offsetX
val top = r2[1] + offsetY
result.set(left, top, left + measuredWidth, top + measuredHeight)
return result
}
/**
* 导航栏的高度(如果显示了)
*/
internal fun navBarHeight(context: Context): Int {
var result = 0
if (context is Activity) {
val decorRect = Rect()
val windowRect = Rect()
context.window.decorView.getGlobalVisibleRect(decorRect)
context.window.findViewById<View>(Window.ID_ANDROID_CONTENT)
.getGlobalVisibleRect(windowRect)
if (decorRect.width() > decorRect.height()) {
//横屏
result = decorRect.width() - windowRect.width()
} else {
//竖屏
result = decorRect.bottom - windowRect.bottom
}
}
return result
}
fun Collection<*>?.size() = this?.size ?: 0
/**判断2个列表中的数据是否改变过*/
internal fun <T> List<T>?.isChange(other: List<T>?): Boolean {
if (this.size() != other.size()) {
return true
}
this?.forEachIndexed { index, t ->
if (t != other?.getOrNull(index)) {
return true
}
}
return false
}
fun Int.isHorizontal() = this == LinearLayout.HORIZONTAL
fun Int.isVertical() = this == LinearLayout.VERTICAL
internal fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = true): View {
if (layoutId == -1) {
return this
}
val rootView = LayoutInflater.from(context).inflate(layoutId, this, false)
if (attachToRoot) {
addView(rootView)
}
return rootView
}

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