반응형

 

 

Permissions.class

public class Permissions {

    public static boolean hasPermissions(Context context, String... permissions) {
        if (context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }
}

 

사용

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

	//사용하려는 퍼미션을 배열에 담아둔다.
    int PERMISSION_ALL = 1000;
    String[] PERMISSIONS = {
            Manifest.permission.CAMERA,
            android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
            android.Manifest.permission.READ_EXTERNAL_STORAGE,
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		
        //만들어놓은 util에서 퍼미션이있는지 물어본다.
        if(!Permissions.hasPermissions(this, PERMISSIONS)){
            ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_ALL);
        }

    }




    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (PERMISSION_ALL == requestCode){
          for (int i : grantResults){
          	  //허용
              if(i == PackageManager.PERMISSION_GRANTED){
                    Log.i(TAG,"## PERMISSION_GRANTED");
              }
			  //거부
              else {
                  Log.i(TAG,"## PERMISSION_DEN");
              }
          }
        }
    }
}
반응형
반응형

2019년 이번해는 정기구독 서비스들이 대세를 이뤘다. 이에 맞춰 정기구독기능을 추가해달라는 요청이 들어와 붙혀보고 정리하는 시간을 가질까한다.

 

 

앱스토어 준비

<그림 1>

-이미 플레이스토어에 앱이 올라와있다고 가정하고 진행하겠다.

- 앱정보 -> 인앱상품 -> 구독 으로 가면 해당 페이지가 뜬다.

- 구독 만들기를 눌러 새로운 구독 아이템을 만들어보자.

 

<그림 2>

 다른부분은 일반적으로 작성하면된다. 그중 중요하게 봐야는 부분은 '제품 ID'인데 추후에 앱에서 연동할때 쓰이는 key 값이기 때문에 잘 기억해 둬야한다.(중요)

<그림 3>

 -<그림2>을 보면 가격 추가부분이 있다.

이부분을 누르면<그림3>이 나오고 가격을 책정하면 자동으로 10프로 부가세 붙어 가격이 책정된다.

- 나머지 부분은 너무 쉽기때문에 생략하고 작성완료하면 <그림1>과 같이 아이템이 생성된다.

 


앱과연동

gradle,manifest에 추가

dependencies {
    //billing
    implementation 'com.android.billingclient:billing:2.0.3'
    implementation 'com.google.code.gson:gson:2.8.6'

}

-gradle dependencies에 추가해 라이브러리를 불러온다.

 

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
	....
    <uses-permission android:name="com.android.vending.BILLING" />
    
        <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
    	.....
        </application>

</manifest>

- use permission에 com.android.vending.BILLNG을추가해준다.

 

핸들러

    public Handler billingHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            //스토어와 연결
            case Common.HANDLER_BILLING_INIT://1
                initBilling();
                break;
                // 연결된 상태에서 구독 상품 가저오기
            case Common.HANDLER_BILLING_GET_CONTENTS://2
                getContentsList();
                break;
                //가저온 구독 상품을 화면에 보여주기
            case Common.HANDLER_BILLING_SHOW_PURCHASE:
                showBilling(userSkuDetails);
                break;
                //스토어 앱캐시로 체크(첫번째 체크 방법)
            case Common.HANDLER_BILLING_CHECK_PERCHASE:
                checkPurchaseAppCache();
                break;
                //스토어와 통신한뒤 체크(두번째 체크 방법)
            case Common.HANDLER_BILLING_HISTORY_PURCHASE:
                checkPurchaseHttpConection();
                break;
                //통신 실패시 처리
            case Common.HANDLER_BILLING_CONNECTION_FAIL:
                unstableServer();
                break;

        }
        }
    };

- 핸들러로 정확하게 동작을 제어해준다.

 

 

 

결제콜백 만들어주기

  //purchase callback
    private PurchasesUpdatedListener purchasesUpdatedListener = (BillingResult billingResult, @Nullable List<Purchase> purchases)->{
        //성공
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null){
            for (Purchase purchaseItem: purchases){
                handlePurchase(purchaseItem);//구매 정보
                confirmPerchase(purchaseItem);//구매 확정

            }
        }

        //취소
        else if (BillingClient.BillingResponseCode.USER_CANCELED == billingResult.getResponseCode()){
         
        }

        //에러
        else {
          
        }
    };

-결제 한뒤 결과를 알려주는 listener이다.

-성공 : 성공한뒤에는 구매정보는 서버에 저장시켜놓고, 구매확정을 해야 최종 결제 승인이된다(안할경우 자동 취소됨)

-취소  

-에러 

 

 

구매 정보

    private void handlePurchase(Purchase purchase) {
        String purchase_json= purchase.getOriginalJson();
		// 원하는 데이터를 가공해 서버에 저장시킨다
    }

- json으로 구매 데이터가 들어오는데 이부분을 서버측에 저장시킨다.

 

 

구매 확정

    //구매확정
    public void confirmPerchase(Purchase purchase) {
        //PURCHASED
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
            if (!purchase.isAcknowledged()) {
                AcknowledgePurchaseParams acknowledgePurchaseParams =
                        AcknowledgePurchaseParams.newBuilder()
                                .setPurchaseToken(purchase.getPurchaseToken())
                                .build();
                billingClient.acknowledgePurchase(acknowledgePurchaseParams, (BillingResult billingResult) ->{
                  	// 구매확정콜백

                });
            }
        }
        //PENDING
        else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
			//구매 유예

        }
        else {
            //구매확정 취소됨(기타 다양한 사유...)

        }
    }

 

 

스토어와 연결

 private BillingClient billingClient;

 public void initBilling(){
        billingClient = BillingClient.newBuilder(mContext)
                .enablePendingPurchases()
                .setListener(purchasesUpdatedListener)
                .build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                //연결 성공
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    billingHandler.sendEmptyMessage(Common.HANDLER_BILLING_GET_CONTENTS);//상품리스트 가저오기
                }
                //연결 실패
                else{
                    billingHandler.sendEmptyMessage(Common.HANDLER_BILLING_CONNECTION_FAIL);
                }
            }
			//연결 끊김
            @Override
            public void onBillingServiceDisconnected() {
			
            }
        });
 }

- 위에서 만들엇던 listner를 setListner부분에 붙혀준다.

-연결성공 : 연결이 성공되면 그커넥션을 가지고 상품 리스트를 가저와야한다.

-연결실패 : 하면서 연결이 실패된적은 없엇던것같다.

-연결끊김 

 

상품가저오기

    public void getContentsList(){
        List<String> sku_contents_list = new ArrayList<String>();
        sku_contents_list.add(itemName);
        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        params.setSkusList(sku_contents_list).setType(BillingClient.SkuType.SUBS);//결제타입 지정
        SkuDetailsResponseListener listener = new SkuDetailsResponseListener() {
            @Override
            public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                //연결을 못함.
                if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK){
                    return;
                }

                //상품정보를 가저오지 못함 -
                if (skuDetailsList == null){
                    return;
                }

                //상품사이즈 체크
                L.i("## response data size : " + skuDetailsList.size());

                //상품가저오기 : 정기결제상품 하나라서 한개만 처리함.
                try {
                    for (SkuDetails skuDetails : skuDetailsList) {
                        String title = skuDetails.getTitle();
                        String sku = skuDetails.getSku();
                        String price = skuDetails.getPrice();
                        userSkuDetails = skuDetails;

                        if (itemName.equals(sku)){
                            showBilling(skuDetails);
                        }
                    }
                }
                catch (Exception e){
                    L.e("## 리스트 가저오기 오류" + e.toString());
                }

            }

        };
        billingClient.querySkuDetailsAsync(params.build() , listener);
    }

- 결제타입 지정 : BillingClient.SkuType.SUBS, BillingClient.SkuType.INAPP 두 가지중 우리는 정기구독을 만들것이기 때문에 BillingClient.SkuType.SUBS를 사용한다.

-itemName은 <그림2>에서 언급한 key값을 넣어주면된다.

- 상품가저온 뒤엔 skuDetails를 가지고 상품을 보여줘야한다. ->showBilling(skuDetails)

 

결제화면 보여주기

    //결제화면 보여주기
    private void showBilling(SkuDetails skuDetails) {
        // Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
        BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                .setSkuDetails(skuDetails)
                .build();
        billingClient.launchBillingFlow(mActivity,flowParams);
    }

- google 라이브러리내 내장된 결제 화면이 나온다.

- purchaseUpdateListner에 결제 결과가 나온다.

- 작동시키면 아마 화면은 뜨지만 결제 화면은 안뜰것이다. 뒤쪽에 설명하겠다.

 

결제상태 체크

 public void checkPurchase() {
        billingClient = BillingClient.newBuilder(mContext)
                .enablePendingPurchases()
                .setListener(purchasesUpdatedListener)
                .build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                //connection success
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {


                    //check query
                    Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);

                    int list = purchasesResult.getPurchasesList().size();
                    //앱스토어에서 정기결제 구독한 사람
                    if (list>=0){
                        for (Purchase purchase : purchasesResult.getPurchasesList()){
                           
                            Gson gson = new Gson();
                            BillingStateVO bsVO = gson.fromJson(purchase.getOriginalJson(), BillingStateVO.class);
                            boolean isPerchase = false;
                            isPerchase =  pakageName.equals(bsVO.getPackageName())&&itemName.equals(bsVO.getProductId());
                            //해당앱 정기 구독한 사람
                            if (isPerchase){
                                //정기구독 유지중인 사람
                                if (bsVO.isAutoRenewing()){

                                }
                                //정기구독 취소한사람
                                else {
                                   
                                }
                            }
                            //해당앱 정기구독 구입한적이 없는 사람
                            else {
                               
                            }

                        }
                    }
                    //앱스토어에서 정기결제 한번도 한적 없는 사람
                    else {
                    }

                }
                //connection fail
                else{
                
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
// 
            }
        });


    }

- 필요할때 상태를 체크해 서버에 업데이트를 한다.

 

결제상태 체크 2번째

콜백

    public interface CheckPurchaseCallback{
        void onSubscribe(BillingStateVO bsVO);
        void onSubscribePending();
        void onSubscribeEND(BillingStateVO bsVO);
        void onNotPurchased(String msg);
        void onUnstableServer();
    }

구현

 public static void checkPurchaseAppCache(
     Context context 
     ,String itemID 
     ,CheckPurchaseCallback callback) 
     {
        BillingClient  cBillingClient = BillingClient.newBuilder(context)
                .enablePendingPurchases()
                .setListener((BillingResult billingResult, @Nullable List<Purchase> purchases)->{

                })
                .build();
        cBillingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                //connection success
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    //check query
                    Purchase.PurchasesResult purchasesResult = cBillingClient.queryPurchases(BillingClient.SkuType.SUBS);
                    List<Purchase> list = purchasesResult.getPurchasesList();

                    //앱스토어에서 정기결제 구독한 사람
                    if (list.size() > 0 ){
                    	//제품이 하나라서 한개만 처리
                        Purchase purchase = list.get(0);
                        Gson gson = new Gson();
                        BillingStateVO bsVO = gson.fromJson(purchase.getOriginalJson(), BillingStateVO.class);
                        boolean isPerchase = false;
                        isPerchase =  itemID.equals(bsVO.getPackageName())&&itemID.equals(bsVO.getProductId());
                        // 정기 구독한 사람
                        if (isPerchase){

							//시간비교 
                            long diff = DateUtils.currentTime() - bsVO.getPurchaseTime();
                            int diffDays = (int)(diff/(24*60*60*1000));

                            switch (bsVO.getPurchaseState()){
                                //구독중 ...
                                case LifeInAppConfig.PURCHASE_STATE_PURCHASED:
                                    callback.onSubscribe(bsVO);
                                    break;

                                // 구독이 끝나도 30일간 사용 가능
                                case LifeInAppConfig.PURCHASE_STATE_CANCELED:
                                    //30일 지나감
                                    if (diffDays>30){
                                        callback.onSubscribeEND(bsVO);
                                    } else {
                                        callback.onSubscribe(bsVO);
                                    }
                                    break;

                                // 결제 수단문제로 구매 보류가 이뤄졌을때 ...
                                case LifeInAppConfig.PURCHASE_STATE_PENDING:
                                    callback.onSubscribePending();
                                    break;
                            }
                        }
                        // 정기구독 구입한적이 없는 사람
                        else {
                            callback.onNotPurchased("auto Renewing NOT YET");
                        }
                    }
                    //앱스토어에서 정기결제 한번도 한적 없는 사람
                    else {
                        callback.onNotPurchased(" auto renew nothing ");
                    }

                }
                //connection fail
                else{
                    callback.onUnstableServer();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {

            }
        });
    }

purchase state : 중요한 녀석이다. 결제상태를 확인할 수 있다.

0 :  결제상태

1  :  취소상태

2 :  결제 보류 상태

 

 

 

사용

 private void checkInAppbilling(){
        LifeInAppBilling.checkPurchaseAppCache(
                this
                ,AppConfig.BILLING_ITEM
                ,new CheckPurchaseCallback() {
            @Override
            public void onSubscribe(BillingStateVO bsVO) {
                bsVO.printVO();

					
                if (!bsVO.isAutoRenewing()){
                    //정책에 따라 자동갱신안한경우 자동갱신하도록 유도
                }
            }
            @Override
            public void onSubscribePending() {
               //보류
            }
            @Override
            public void onSubscribeEND(BillingStateVO bsVO) {
                bsVO.printVO();
            }

            @Override
            public void onNotPurchased(String msg) {
               
            }

            @Override
            public void onUnstableServer() {
        
            }
        });
    }

 

 

- interface callback을 수정해 원하는 콜백으로 구현하면 됩니다.

BillingStateVO

// 상태조회시 넘어오는 값들.
public class BillingStateVO {

    private String orderId;
    private String packageName;
    private String productId;
    private long purchaseTime;//long
    private String purchaseState;//int
    private String purchaseToken;
    private boolean autoRenewing;//boolean
    private boolean acknowledged;//boolean

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String pakageName) {
        this.packageName = pakageName;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public long getPurchaseTime() {
        return purchaseTime;
    }

    public void setPurchaseTime(long purchaseTime) {
        this.purchaseTime = purchaseTime;
    }

    public String getPurchaseState() {
        return purchaseState;
    }

    public void setPurchaseState(String purchaseState) {
        this.purchaseState = purchaseState;
    }

    public String getPurchaseToken() {
        return purchaseToken;
    }

    public void setPurchaseToken(String purchaseToken) {
        this.purchaseToken = purchaseToken;
    }

    public boolean isAutoRenewing() {
        return autoRenewing;
    }

    public void setAutoRenewing(boolean autoRenewing) {
        this.autoRenewing = autoRenewing;
    }

    public boolean isAcknowledged() {
        return acknowledged;
    }

    public void setAcknowledged(boolean acknowledged) {
        this.acknowledged = acknowledged;
    }


    public void printVO(){
        L.i("\n## orderId : "+orderId +
                "\n pakageName : "+packageName +
                "\n productId : "+productId +
                "\n purchaseTime : "+purchaseTime +
                "\n purchaseState : "+purchaseState +
                "\n purchaseToken : "+purchaseToken +
                "\n autoRenewing : "+autoRenewing +
                "\n acknowledged : "+acknowledged +"\n"
                );
    }
}

 

 

알파테스트

- 앱버전 -> 관리

 

-새 목록 만들기

- 테스트 참여 URL 복사한뒤 구글아이디가 로그인된 브라우저(휴대폰)에서 해당 url로 이동

- 테스트 참여 시작 클릭!(

-몇분뒤 테스터로 등록되고 플레이 스토어 들어가보면 [ 앱이름 (베타,알파) ]로 이름이 바뀌어있다,  테스트 앱 등록 완료

- 결제 테스트 진행!

 

 

 

 

테스트

여기다 이메일 추가하면 15분 정도뒤 테스터로 등록되 실제 결제 하듯이 결제할수있다.

 

 

일회성 제품 및 정기 결제의 구매 흐름은 비슷하지만, 정기 결제에는 정기 결제 갱신의 성공 또는 거부와 같은 추가 시나리오가 있습니다. 두 가지 상황 모두에서 애플리케이션을 테스트하는 데 도움이 되도록 '테스트 도구, 항상 승인' 및 '테스트 도구, 항상 거부' 결제 수단을 사용할 수 있습니다. 성공적인 정기 결제 시나리오 이상의 시나리오를 테스트하려면 이러한 결제 수단을 사용하세요.

테스트 정기 결제 갱신

테스트 정기 결제는 테스트를 돕기 위해 일반적인 경우보다 더 빨리 갱신됩니다. 다음 표에는 기간이 다양한 정기 결제의 테스트 갱신 시간이 나와 있습니다.

참고: 이러한 시간은 대략적인 것으로, 이벤트의 정확한 시간에는 약간의 변동이 있을 수 있습니다. 이러한 변동을 보완하려면 각 정기 결제 만료일 이후 API를 호출하여 현재 상태를 확인하세요.

프로덕션 정기 결제 기간테스트 구독 갱신

1주 5분
1개월 5분
3개월 10분
6개월 15분
1년 30분

참고: 테스트 정기 결제는 최대 6회 갱신됩니다.

반응형
반응형

xml 파일에 ui를 구성하다보면 반복되는 edittext나 button 들이 있다.

깔끔한 코딩과 재사용성을 높힌 커스텀 위젯을 만들어보자.

 

 

결과

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="2sp">

    <LinearLayout
        android:id="@+id/mor_et_title_linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginBottom="5sp">

        <TextView
            android:id="@+id/mor_et_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:text="Title"/>
        <TextView
            android:id="@+id/mor_title_need"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textColor="@android:color/holo_red_dark"
            android:layout_marginLeft="5dp"
            android:text="*"/>
    </LinearLayout>


    <EditText
        android:id="@+id/mor_et_edittext"
        android:layout_width="match_parent"
        android:layout_height="50sp"
        android:background="@drawable/mor_edittext_border"
        android:paddingLeft="10dp"
        android:singleLine="true"
        android:hint="hint"/>

    <LinearLayout
        android:id="@+id/mor_et_reference_linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/mor_reference_need"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textColor="@android:color/holo_red_dark"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_marginRight="5dp"
            android:text="·"/>
        <TextView
            android:id="@+id/mor_et_reference"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="#BEBEBE"
            android:text="reference"/>
    </LinearLayout>



</LinearLayout>

- 우선 최종본이다 , textview / edittext / textview  순으로 제목/ 내용쓰는곳 /간단한 설명을 상황에따라 문구를 수정할수 있는 edittext 위젯을 만들것이다.

 

1.속성정의

<resources>
    <declare-styleable name="morET">
        <attr name="bg" format="reference|integer" />
        <attr name="symbol" format="reference|integer" />
        <attr name="title" format="reference|string" />
        <attr name="placeholder" format="reference|string" />
        <attr name="belowExplain" format="reference|string" />
        <attr name="textColor" format="reference|integer" />
        <attr name="editable" format="reference|boolean" />
    </declare-styleable>
</resources>

 

수정하려는 부분을 name에 정의 해놓으면

    <com.han.testkotlin.CustomView.MorEditText
        android:id="@+id/first_moreditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title = "타이틀입니다."
        app:placeholder = "my hint"
        app:belowExplain = "surname과 "/>

이런식으로 사용이 가능하다.

 

 

2.커스텀 위젯 클래스 만들기

 

class MorEditText :LinearLayout {

//    속성 : bg           타입 : integer
//    속성 : symbol       타입 : integer
//    속성 : title        타입 : string
//    속성 : placeholder  타입 : string
//    속성 : belowExplain 타입 : string
//    속성 : textColor    타입 : integer
//    속성 : editable     타입 : boolean

    constructor(context: Context?) : super(context) {
        initLayout()
    }
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        initLayout()
        if (attrs != null) {
            getAttrs(attrs)
        }
    }
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ){
        initLayout()
        if (attrs != null) {
            getAttrs(attrs,defStyleAttr)
        }
    }


    private fun initLayout() {
        LayoutInflater.from(context).inflate(R.layout.mor_edittext, this, true)
    }

    private fun getAttrs(attrs : AttributeSet) {
         val typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.morET ) as TypedArray

        setTypeArray(typedArray);
    }


    private fun getAttrs(attrs : AttributeSet,defStyle:Int) {
        val typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.morET, defStyle, 0) as TypedArray
        setTypeArray(typedArray);

    }

    private fun setTypeArray( typedArray:TypedArray) {

        //Title
        val strTitle = typedArray.getString(R.styleable.morET_title)
        if (strTitle != null){
            mor_et_title_linearLayout.visibility = View.VISIBLE
            mor_et_title.setText(strTitle)
        } else {
            mor_et_title_linearLayout.visibility = View.GONE
            mor_et_title.setText("")
        }


        //Hint
        val strHint = typedArray.getString(R.styleable.morET_placeholder)
        if (strTitle != null){
            mor_et_edittext.setHint(strHint)
        } else {
            mor_et_edittext.setHint("")
        }



        //reference
        val strRef = typedArray.getString(R.styleable.morET_belowExplain)
        if (strRef != null){
            mor_et_reference_linearLayout.visibility = View.VISIBLE
            mor_et_reference.setText(strRef)
        } else {
            mor_et_reference_linearLayout.visibility = View.GONE
            mor_et_reference.setText("")
        }


        typedArray.recycle();
    }

    fun setMor_et_title(s:String){
        mor_et_title.setText(s)
    }

    fun setmor_reference_need(s:String){
        mor_reference_need.setText(s)
    }

    fun setmor_et_edittext(s:String){
      mor_et_edittext.setText(s)
    }

    fun getmor_et_edittext():String{
        return mor_et_edittext.text.toString()
    }

}

- 이전에 android 자체적으로 생성되어있는 widget들도 이런식으로 생성자를 거처 생성된다.

- initLayout : 커스텀할 layout을 inflater로 가저와 지정해준다.

- setTypeArray에 typeArray.getString(위쪽)에서는 app:title = "text"(아래쪽)이라고 할때 text를 String 값으로 리턴해준다.

- string 값으로 리턴해준 이유는 이전 declare-stleable 에 format 부분을 "reference|string"지정했기 때문이다

<attr name="title" format="reference|string" />

 

- 이제 return값을 실제 위젯이랑 연결 시켜준다 TextView.setText( return값)

- 지정해준뒤에는 재사용을 위해 typedArray.recycle()을 해줘야한다. 

 

 

3.사용해보기

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:layout_margin="20sp">

    <com.han.testkotlin.CustomView.MorEditText
        android:id="@+id/first_moreditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title = "타이틀입니다."
        app:placeholder = "my hint"
        app:belowExplain = "surname과 "/>

    <com.han.testkotlin.CustomView.MorEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title = "타이틀2 입니다."
        app:placeholder = "my hint"/>

    <com.han.testkotlin.CustomView.MorEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title = "타이틀2 입니다."
        app:placeholder = "my hint"/>

    <com.han.testkotlin.CustomView.MorEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title = "타이틀2 입니다."
        app:placeholder = "my hint"/>

    
    <com.han.testkotlin.CustomView.MorEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title = "타이틀2 입니다."
        app:placeholder = "my hint"/>

    <Button
        android:id="@+id/textbtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="click"/>
</LinearLayout>

 

-짧은 코드로 여러개의 custom editetext를 만든것을 볼 수 있다.

반응형
반응형

최근 다국어로된 프로젝트를 맡은적이 있는데 검색을 해봐도 언어 파일을 추가하는 법이 잘 안나와서 작성해본다.

 

1. xml 파일 생성

 

실제 디렉토리 수준에서 보면 이런식이로 생성된다.

-저는 strings라는 이름으로 처음 생성했기때문에 다른 언어도 통일해서 사용중입니다.

-strings폴더안에 묶여있는데 같은 이름으로 생성하면 자동으로 android studio가 묶어줍니다.

2.Available qualifiers -> Locale 클릭후 원하는 국가의 국기를 클릭(화면엔 눌러서 없음)

- 국기 뒤쪽에 es , et 등 약자들이 있는데 추후에 언어설정의 키값이 되니 잘봐두도록 하자.

 

language.xml (ko-rKR)

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="menu1">메뉴1</string>
    <string name="menu2">메뉴2</string>
    <string name="menu3">메뉴3</string>
    <string name="menu4">메뉴4</string>
    <string name="menu5">메뉴5</string>
</resources>

 

language.xml (en-rUS)

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="menu1">menu1</string>
    <string name="menu2">menu2</string>
    <string name="menu3">menu3</string>
    <string name="menu4">menu4</string>
    <string name="menu5">menu5</string>
</resources>

 

- 파일명, name 명을 일치 시켜줘야된다.

- 괄호에 ko-rKR,en-rUS에서 각각 ko,en이 키값이 되서 언어를 변경할수있도록 해준다.

 

 

    private void changeLocale(String localeLang){

        Locale locale = null;

        switch (localeLang){
            case "ko":
                locale = new Locale("ko");
                break;

            case "en":
                locale = new Locale("en");
                break;
        }
        Configuration config = context.getResources().getConfiguration();

        if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ) {
            config.setLocale(locale);
        }
        else {
            config.locale = locale;
        }

        getResources().updateConfiguration(config, getResources().getDisplayMetrics());

        list.clear();
        list.add(getString(R.string.menu1));
        list.add(getString(R.string.menu2));
        list.add(getString(R.string.menu3));
        list.add(getString(R.string.menu4));
        list.add(getString(R.string.menu5));
        pa.notifyDataSetChanged();
    }

 

 

-localeLang 파라미터로 ko,en이 들어오면 각각 한국어 영어로 변경해준다.

반응형

+ Recent posts