Compare commits
70 Commits
gong_dev_a
...
v681小游戏
| Author | SHA1 | Date | |
|---|---|---|---|
| d863c0af5a | |||
| 99c1037a15 | |||
| 50686957dc | |||
| 60485deed5 | |||
| 6eee7b9ede | |||
| e8e0fc32f9 | |||
| 3d4ad99c99 | |||
| a25e22b142 | |||
| 3aeabfa32b | |||
| 47da21351e | |||
| 451a875526 | |||
| dc7b987eda | |||
| fe28d3508b | |||
|
|
cb87974320 | ||
|
|
dbd684a6e2 | ||
| abc37aa486 | |||
| 3349b2d7df | |||
| 97d636ddec | |||
| 3b8aedaa17 | |||
| 40ba4b8aa8 | |||
| f7db0b0768 | |||
| ed642f0137 | |||
| d1512bc256 | |||
| 9f90040168 | |||
| 9ae6fedd8d | |||
| 516a068c25 | |||
| 63b7a18c0b | |||
|
|
6af48002bb | ||
| 79c730c1b7 | |||
| c20a9804e9 | |||
|
|
96a6b05d03 | ||
| ba8090eec7 | |||
| e1fd4949b5 | |||
|
|
08c46a7684 | ||
| 1cde41f2d0 | |||
| 092d4cb914 | |||
| 21ae621343 | |||
| 048c66736f | |||
| d3055d8fb2 | |||
|
|
b9164b6a08 | ||
|
|
67e5e4e02f | ||
|
|
2351618e5a | ||
|
|
b2e2ca7303 | ||
| 714e51b621 | |||
| efa4c25c4b | |||
|
|
854cad8ec6 | ||
| 46fba9429d | |||
| c67ed4b736 | |||
|
|
4a91aafb4b | ||
|
|
2b20782def | ||
|
|
152848a04f | ||
|
|
fe827482c9 | ||
| 7fc1c2e712 | |||
| 7a6dfe5a3d | |||
|
|
96c45820b0 | ||
| ac18150503 | |||
|
|
22b208bcd9 | ||
|
|
484069dac7 | ||
| ade837e85c | |||
| 12a1f24101 | |||
| 250d4832a3 | |||
|
|
93db808f8b | ||
|
|
59f0fa4acb | ||
| 8fe6130c81 | |||
| 9292e6f5c5 | |||
| 845b217c4d | |||
|
|
7edd75e223 | ||
|
|
eb9f615f70 | ||
| 9c38f40098 | |||
| 8cae89e7cd |
@@ -334,4 +334,7 @@
|
||||
<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>
|
||||
|
||||
@@ -31,4 +31,6 @@ repositories {
|
||||
}
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
// ========================================================================
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
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;
|
||||
}
|
||||
// ====================================================================
|
||||
}
|
||||
}
|
||||
@@ -1,642 +0,0 @@
|
||||
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>
|
||||
* <activity android:name="com.sec.android.iap.lib.activity.PaymentActivity" android:theme="@style/Theme.Empty"
|
||||
* android:configChanges="orientation|screenSize"/>
|
||||
* </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>
|
||||
* <activity android:name="com.sec.android.iap.lib.activity.PaymentActivity" android:theme="@style/Theme.Empty"
|
||||
* android:configChanges="orientation|screenSize"/>
|
||||
* </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();
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.samsung.android.sdk.iap.lib.listener;
|
||||
|
||||
/**
|
||||
* Created by sangbum7.kim on 2018-02-28.
|
||||
*/
|
||||
|
||||
public interface OnSucceedBind {
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -52,4 +52,8 @@
|
||||
<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>
|
||||
|
||||
@@ -52,4 +52,8 @@
|
||||
<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>
|
||||
|
||||
@@ -52,4 +52,8 @@
|
||||
<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>
|
||||
|
||||
@@ -52,4 +52,8 @@
|
||||
<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>
|
||||
|
||||
@@ -20,5 +20,7 @@
|
||||
<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>
|
||||
2
SVGAlibrary/.gitignore
vendored
Normal file
2
SVGAlibrary/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
*.iml
|
||||
37
SVGAlibrary/build.gradle
Normal file
37
SVGAlibrary/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
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'
|
||||
}
|
||||
17
SVGAlibrary/proguard-rules.pro
vendored
Normal file
17
SVGAlibrary/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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 *;
|
||||
#}
|
||||
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<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>
|
||||
@@ -0,0 +1,9 @@
|
||||
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)
|
||||
}
|
||||
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
@@ -0,0 +1,119 @@
|
||||
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")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
/**
|
||||
* Created by miaojun on 2019/6/21.
|
||||
* mail:1290846731@qq.com
|
||||
*/
|
||||
interface SVGAClickAreaListener{
|
||||
fun onClick(clickKey : String)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,565 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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?
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
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]!!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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)
|
||||
@@ -0,0 +1,28 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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?)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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>
|
||||
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">SVGAPlayer</string>
|
||||
</resources>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
18
Share/src/main/res/values-zh/strings.xml
Normal file
18
Share/src/main/res/values-zh/strings.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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>
|
||||
@@ -1,17 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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>
|
||||
<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>
|
||||
</resources>
|
||||
1
TabLayout/.gitignore
vendored
Normal file
1
TabLayout/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
37
TabLayout/build.gradle
Normal file
37
TabLayout/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
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"
|
||||
@@ -18,4 +18,4 @@
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,4 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
/>
|
||||
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
@@ -0,0 +1,200 @@
|
||||
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="基类方法">
|
||||
}
|
||||
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
@@ -0,0 +1,275 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
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()!!
|
||||
}
|
||||
}
|
||||
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
@@ -0,0 +1,215 @@
|
||||
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
|
||||
}
|
||||
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
@@ -0,0 +1,438 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
@@ -0,0 +1,222 @@
|
||||
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
|
||||
)
|
||||
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
@@ -0,0 +1,279 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
@@ -0,0 +1,153 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
@@ -0,0 +1,931 @@
|
||||
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
|
||||
}
|
||||
2042
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
2042
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user