https://www.androidhuman.com/lecture/proguard/2017/02/16/reduce-method-count-with-proguard/

위 링크대로 해봤더니 실제로 메소드가 반이 줄어드는 놀라운 마법이 펼쳐짐...와우

RecyclerView Item에 버튼이나 체크박스가 있을때 발생되는 이벤트를 Adapter에서 처리 할수도 있겠지만, 어쩔수 없이

Activity에서 처리를 해야 할때도 있다...이럴때 구현하는 방법을 남겨본다...이렇게 라도 안하면...까먹어서..ㅠㅠ

내가 알고 있는 방법은 두가지가 있다. 첫번째를 Tag를 이용하는 방법과, 이벤트 리스너를 만들어서 하는 방법이 있는데,

이벤트 리스너를 만들어서 처리 하는 방법으로 남긴다.

우선 class가 하나 필요 하다.

(activity에 inner 클래스로 만들어도 된다.)

public class ListEventListener {

private OnViewItemClickListener onViewItemClickListener;

public interface OnViewItemClickListener{
public void onViewItemClick(Map<String, String> item);
}
}

리스트에 있는 특정 view에서 이벤트가 발생이 되면 해당 item에 대한 데이터 전체를 받을 거라서 인자값을 Map으로 했다..


Activity에서는 아래 처럼 이벤트가 발생 했을때 처리할 리스너를 구현해 준다.

ListEventListener.OnViewItemClickListener onViewClickListener =
new ListEventListener.OnViewItemClickListener() {
@Override
public void onViewItemClick(Map<String, String> item) {
Toast.makeText(OrderAddActivity.this, "이벤트 테스트", Toast.LENGTH_SHORT).show();
}
};

그리고 RecyclerView Adapter 생성자에 onViewClickListener를 넘겨주고

OrderAddAdapter adapter = new OrderAddAdapter(this, onViewClickListener);


Adapter에서는 아래처럼 이벤트가 발생했을때 인자로 넘긴 이벤트 리스너를 호출해 주면 Activity에 구현된 

이벤트 리스너가 실행이 된다...

holder.ivDelete.setOnClickListener { v ->

onViewClickListener.onViewItemClick(item)
}


알면 참 쉬운데...모르면 개고생 인듯...ㅡㅡ;;;

listview, recyclerview를 사용하면서 리스트 안에 checkbox를 넣어야 할경우

체크된 상태를 유지를 못하게 된다.

대부분 아래 코드와 같을 거라 본다...

holder.cbFavoritItem.setOnCheckedChangeListener(
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->

if (holder.cbFavoritItem.isPressed){
onCheckedChange.onViewCheckedChange(isChecked, item)
}
})

이렇게만 해서 리스트를 구현을 하게 되면 나타나는 증상은 체크박스에 체크를 하고 리스트를 아래로 스크롤을 하고 다시 체크한 항목이 있는 위치로 오게 되면 체크가 풀려 있는 현상이 나타나게 되거나 또는 스크롤을 아래로 내리다 보면 체크가 안되어 있는 항목이 체크 되어 있는 등의 증상들이 나타나게 된다. 


holder.cbFavoritItem.setOnCheckedChangeListener(null)

이럴때는 위 코드 처럼 이벤트를 등록하기 전에 null로 초기화 후 이벤트를 등록해 주면 된다.

만약에 리스트에 들어갈 데이터에 특정 상태값에 따라 체크를 바꿔줘야 한다면, 


holder.cbFavoritItem.isPressed

위 코드를 통해서 사용자에 의해 발생된 이벤트인지 여부를 판단해서 처리해 줘야 하는것도 까묵지 말자...


전체 소스중에 해당되는 부분만 추려 보면 아래와 같다.


holder.cbFavoritItem.setOnCheckedChangeListener(null)

if (isFavorit == "T") {
holder.cbFavoritItem.setChecked(true)
}
else{
holder.cbFavoritItem.setChecked(false)
}

holder.cbFavoritItem.setOnCheckedChangeListener(
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->

if (holder.cbFavoritItem.isPressed){
onCheckedChange.onViewCheckedChange(isChecked, item)
}
})


android 6.0이 되면서 부터 퍼미션을 사용자가 부여 할수 있게 되었다.

그래서 이번에 업데이트를 하면서 기존에 있던 앱들의 권한을 변경하게 되면서 알게된 라이브러리를 적어둔다.

예로 카메라 퍼미션 체크를 해본다.

아래 소스는 카메라로 바코드를 읽기 위해서 사용한 코드의 일부이고, 바코드 라이브러리는 zxing을 사용했다.


https://github.com/ParkSangGwon/TedPermission

위 사이트로 가면 사용법이 나와 있으니 참고 하면 되고,


사용법을 적자면...

    PermissionListener permissionlistener = new PermissionListener() {
@Override
public void onPermissionGranted() {
IntentIntegrator integrator = new IntentIntegrator(ProductListActivity.this);
integrator.setCaptureActivity(BarcodeScannerActivity.class);
integrator.setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES);
integrator.setOrientationLocked(false);
integrator.setBeepEnabled(true);
integrator.initiateScan();
}

@Override
public void onPermissionDenied(ArrayList<String> deniedPermissions) {
// Toast.makeText(ProductListActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
}
};

우선 리스너를 위와 같이 등록한다. 


버튼의 onClickListener에 

new TedPermission(this)
.setPermissionListener(permissionlistener)
.setDeniedMessage("바코드 등록 기능을 사용하기 위해서는 카메라를 사용할수 있는 권한이 필요 합니다. 설정으로 이동후 카메라 권한을 활성화 시켜 주세요.")
.setPermissions(Manifest.permission.CAMERA)
.check();

이렇게 해주면 된다.


자세한 설명은 만드신 분께서 블로그(http://gun0912.tistory.com/55)에 친절하게 설명을 해놓으셔서 블로그에 방문해서 보면 될거 같다.


이번에 프로젝트를 하면서 이미지를 무한으로 돌려야 하는 요구사항이 발생하여 이것저것...여기저기 소스 받아 보고 분석 해보면서

두가지의 스크롤 방법이 있다는걸 알았다...나중에라도 또 써먹을일이 있을을거 같아 남긴다...

요구사항

첫번째 : 이미지 3~4장을 자동으로 스크롤링 시킬것.

두번째 : 이미지가 마지막에 왔을때 앞으로 이동되는 것이 아니라 마지막에서 자연스럽게 다음 이미지가 있는거 처럼 처음 이미지를 보여줄것(무한 스크롤).

세번째 : 이미지를 탭했을때 해당 이미지의 상세 화면을 보여줄것.

네번째 : 이미지가 자동 스크롤이 되지만 사용자가 수동으로 스크롤을 할수 있어야함.


이렇게 4가지의 요구사항이 있어서 처음엔 ViewFlipper로 구현을 하려고 했었지만, 문제가 발생을 했다.

ViewFlipper로 자동 스크롤도 되고 해당 이미지를 탭했을때 해당 이미지의 상세 페이지로도 넘어 갈수 있었지만, 

터치에서 문제가 됬다. 사용자가 수동으로 스크롤을 할수 없었다. 그래서 StackOverFlow나 국내 안사, 기타 등등 커뮤니티를 전전하면서

해당 문제를 해결 할수 있는 방법을 찾아봤지만 방법을 못찾았다. 그래서 다시 찾았다. 이번엔 ViewFlipper가 아니라 

ViewPager로 찾아 봤다...있다...있어...

무한 스크롤이 구현될수 있는 방법은 두가지다

첫번째.

1,2,3,4 페이지가 있을때 마지막인 4번째 페이지에서 1번째 페이지로 점프를 해서 첫페이지부터 다시 스크롤을 하는 방법.

두번째

1,2,3,4 페이지가 있을때 마지막인 4번째 페이지에서 다음페이지가 있는거 처럼 1페이지를 자연스럽게 다음페이지로 보여주는 방법.


그래서 난 두가지의 소스를 찾았다 

1.자동스크롤을 시켜주는 샘플

2.마지막 페이지에서 자연스럽게 1번째 페이지를 보여주는 샘플

두 샘플을 가지고 믹스를 해서 위 요구사항 4개를 만족하는 기능을 구현할수 있었다.

그리고 요구사항에 있는 마지막 페이지에서 첫 페이지를 자연스럽게 다음 페이지가 있는거처럼 보여주는 샘플 외에도,

추가로 마지막 페이지에서 앞으로 점프를 해서 첫페이지 부터 다시 보여주는 샘플도 구할수가 있었다.

사실 ViewPager의 Adapter만 바꿔주면 된다.


원본 링크:

자동 스크롤이 가능한 ViewPager

https://github.com/Trinea/android-auto-scroll-view-pager


마지막 페이지에서 자연스럽게 다음페이지가 있는거 처럼 첫 페이지를 보여주는 샘플

https://github.com/antonyt/InfiniteViewPager


마지막 페이지에서 첫페이지로 점프해서 첫페이지부터 다시 보여주는 샘플

https://github.com/manishkpr/AndroidViewPagerGallery/tree/master/ViewpagerImageGallery


첨부파일은 믹스 위에 샘플을 믹스한 소스이다.

AutoScrollViewPager의 옵션중에 페이지 넘어가는 속도 조절 가능 하고 마지막 페이지에서 계속 스크롤을 할건지 여부를 선택 가능 하고 등 관련 옵션이 몇게 있으니 보면 될거 같다...


AutoScrollViewPager.zip



  1. 파일이 안되요..ㅜ 2016.12.06 09:45

    첨부해주신걸 다운 받은 파일이 안되요..ㅜ

  2. 익명 2017.07.10 16:01

    덕분에 잘 썼습니다. 감사합니다~

안드로이드에서 edittext에서 숫자만 입력받기...

android:inputType="number"
android:digits="0123456789"

edittext에서 inputType를 number로 하면 숫자키패드가 뜨기는 하지만 키패드에 "-"도 포함이 된다...

"-"도 입력받고 싶지 않을때는...이렇게 한다...정규식도 먹을려나??? 흠...나중에 한번...해봐야지...


블로그 여기저기 찾아 다니다가 어느분께서 잘~ 정리해 놓으셨길래...퍼왔음...

출처는 여기 : http://bspfp.pe.kr/376


본문내용

======================================================================================================

BS가 간단한 어플리케이션을 만들고 있는데요.
설정 파일이나, 커스터마이징 파일등을 어디에 저장할지 몰라 좀 찾아 봤습니다.

안드로이드를 사용하는 스마트폰, 태블릿 등을 보면 아이폰과 달리 파티션이 나뉘어 있는데요.
내부 저장소와 외부 저장소로 나뉘어 있고 내부 저장소에는 어플리케이션과 데이터 일부가 저장되고
외부 저장소에는 데이터가 저장되도록 되어 있습니다.
물론 어플리케이션 개발자가 내부 저장소에 모두 저장하게 만든다면... 내부 저장소 공간을 홀랑 쓰게 되겠지만요.
(H/W에 따라서 두 개의 저장소 이외에 SD카드나 외장 USB 드라이브를 지원하기도 합니다.)

내부 저장소는 각 어플리케이션의 private 영역이 존재하고 외부 저장소에는 private 영역도 있고, public 영역도 있습니다.

어플리케이션을 삭제하게 되면 private 영역만 같이 삭제가 되고 public 영역은 여전히 남게 됩니다.
어플리케이션 작성시 이 점을 반드시 유의하여 유저의 데이터가 프로그램 제거시 삭제되지 않게 하고
어플리케이션의 데이터가 삭제시에 남지 않도록 해야 합니다.


문제는 이러한 경로가 현재 시스템에서 어디에 있는가? 입니다.
private 영역에 관한 API는 Context의 메서드로 되어 있고 Activity가 Context를 상속하므로 여기서 가져다 쓰면 됩니다.
public 영역에 관한 API는 Environment의 정적 메서드로 되어 있습니다.
각 경로를 구하는 API가 존재하므로 하나씩 살펴보겠습니다.

  1. 내부 저장소
     
    • 캐시 영역
      File Context.getCacheDir()
      캐시 경로를 반환합니다.
      경로: /data/data/<package name>/cache예: /data/data/bspfp.myfirstapp/cache
       
    • 데이터베이스 파일
      File Context.getDatabasePath(String name)
      name 이름의 데이터베이스 경로를 반환합니다.
      경로: /data/data/<package name>/databases예: /data/data/bspfp.myfirstapp/databases
       
    • 일반 파일 저장 영역
      File Context.getFilesDir()
      어플리케이션에서 사용하는 일반 파일들을 저장하는 경로를 반환합니다.
      경로: /data/data/<package name>/files
      예: /data/data/bspfp.myfirstapp/files
      결과값을 디렉토리가 아닌 특정 파일을 원할 경우에는 아래 API를 사용하면 됩니다.
      File Context.getFileStreamPath(String name)
       
  2. 외부 저장소
     
    • 최상위 경로
      static File Environment.getExternalStorageDirectory()
      외부 저장소 경로를 반환합니다.
      경로: /mnt/sdcard
       
    • public 디렉토리 경로
      static File Environment.getExternalStoragePublicDirectory(String type)
      type에 따른 public 디렉토리 경로를 반환합니다.
      type은 Environment 클래스의 static 필드로 명시되어 있습니다.
      하지만 시스템에 따라 해당 경로가 없을 수도 있습니다.
      경로를 새로 생성하고자 할 경우에는 File.mkdirs() API를 사용하시면 편리합니다.
      필드이름설명경로
      DIRECTORY_ALARMS알람용 오디오 파일/mnt/sdcard/Alarms
      DIRECTORY_DCIM카메라로 촬영한 사진/mnt/sdcard/DCIM
      DIRECTORY_DOWNLOADS다운로드한 파일/mnt/sdcard/Download
      DIRECTORY_MOVIES동영상 파일/mnt/sdcard/Movies
      DIRECTORY_MUSIC음악 파일/mnt/sdcard/Music
      DIRECTORY_NOTIFICATIONS알림음 오디오 파일/mnt/sdcard/Notifications
      DIRECTORY_PICTURES그림/스샷 파일/mnt/sdcard/Pictures
      DIRECTORY_PODCASTS팟캐스트 파일/mnt/sdcard/Podcasts
      DIRECTORY_RINGTONES벨소리 파일/mnt/sdcard/Ringtones
       
    • 어플리케이션 private 데이터 저장 경로
      File Context.getExternalFilesDir(String type)
      type에 따른 어플리케이션 private 디렉토리 경로를 반환합니다.
      type은 Environment에 정의되어 있는 것을 사용합니다.
      필드이름설명경로
      DIRECTORY_ALARMS알람용 오디오 파일/mnt/sdcard/Android/data/<package name>/files/Alarms
      DIRECTORY_MOVIES동영상 파일/mnt/sdcard/Android/data/<package name>/files/Movies
      DIRECTORY_MUSIC음악 파일/mnt/sdcard/Android/data/<package name>/files/Music
      DIRECTORY_NOTIFICATIONS알림음 오디오 파일/mnt/sdcard/Android/data/<package name>/files/Notifications
      DIRECTORY_PICTURES그림/스샷 파일/mnt/sdcard/Android/data/<package name>/files/Pictures
      DIRECTORY_PODCASTS팟캐스트 파일/mnt/sdcard/Android/data/<package name>/files/Podcasts
      DIRECTORY_RINGTONES벨소리 파일/mnt/sdcard/Android/data/<package name>/files/Ringtones
      null상위 경로/mnt/sdcard/Android/data/<package name>/files
       
    • 외부 캐시 영역
      File Context.getExternalCacheDir()
      외부 저장소의 캐시 디렉토리를 반환합니다.
      경로: /data/data/<package name>/cache
      예: /data/data/bspfp.myfirstapp/cache


방금 삽질을 했는데...ㅡㅡ;;;;

TableLayout의 tablerow를 LayoutInflater한번 사용해서 해볼라고 했는데 ListView에서 처럼 하니까 안되더라...

You must call removeView() on the child's parent first. 에러만 열심히 내고...

왜일까? 왜일까? 한참을 고민을 했는데 가만 생각해보니...tablerow는 다른 view과 좀 depth가 다르다 

tablerow는 parentview로 tablelayout을 가지지만 또 childview를 가지게 된다...

그래서 혹시나...해서 LayoutInflater로 xml을 호출 하는 부분에 parentview인 tablelayout을 넘겨줬더니 똬~~~ @.@ 

된다...ㅡㅡ;;;;

View v = LayoutInflater.from(getActivity()).inflate(R.layout.tablerow, tblayout, false);

이렇게...그리고 위에 tablerow.xml 은 TableRow가 rootview로 되어 있다...


그래서 tablelayout에 addView할때는 

v를 걍 인자로 넘기면 된다...

tblayout.addView(v); 이렇게....


public void getSMS(){
final String ACTION = "android.provider.Telephony.SMS_RECEIVED";

IntentFilter filter = new IntentFilter();
filter.setPriority(999); // 우선순위를 높여서 문자를 가장 먼저 인식한다.
filter.addAction(ACTION);

//브로드캐스트 생성
mBroadcastReceiver = new BroadcastReceiver(){

@Override
public void onReceive(Context context, Intent intent){

if (intent.getAction().equals(ACTION)){

Bundle bundle = intent.getExtras();
if (bundle == null)
return;

Object[] pdusObj = (Object[]) bundle.get("pdus");

if (pdusObj == null)
return;

SmsMessage[] smsMessages = new SmsMessage[pdusObj.length];

for (int i = 0; i < pdusObj.length; i++){
smsMessages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
abstrackNumber(smsMessages[i].getMessageBody());
}

this.abortBroadcast();

}

}

};

registerReceiver(mBroadcastReceiver, filter);
}

브로드캐스트리시버는 전역으로 Activity에 선언한다.


private void getAuthNumber(String message){

//담겨진 문자에서 인증 번호만 추출하기 위해 정규식을 사용.
Pattern p = Pattern.compile("([^\\d])");
Matcher matcher = p.matcher(message);
StringBuffer destStringBuffer = new StringBuffer();

while (matcher.find()){
matcher.appendReplacement(destStringBuffer, "");
}

Toast.makeText(this, destStringBuffer, Toast.LENGTH_SHORT).show();
matcher.appendTail(destStringBuffer);
edConfirm.setText(destStringBuffer.toString());
}

실질적으로 sms를 캐치해서 '[' ']' 안에 담긴 인증번로를 가져와서 edConfirm EditText에 보여준다.


출처:

http://junkworld.tistory.com/entry/android-%EB%AC%B8%EC%9E%90-%EC%9D%B8%EC%A6%9D%EB%B2%88%ED%98%B8-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EB%B6%99%EC%9D%B4%EA%B8%B0



P.S createFromPdu가 deprecated인데...귀찮네...나중에 바꿔야지...ㅡㅡ;;;

public boolean textValidate(String str) {
String Passwrod_PATTERN = "^(?=.*[a-zA-Z]+)(?=.*[0-9]+).{1,6}$";
Pattern pattern = Pattern.compile(Passwrod_PATTERN);
Matcher matcher = pattern.matcher(str);
return matcher.matches();
}

비밀번호 체크 할때 영어+숫자+특수문자 혼합해서 6 ~ 18자 사이 비밀번호를 체크 하는 로직이다.

정규식을 사용해서 패턴 체크를 하고 있음...

+ Recent posts