728x90

MVVM 구조의 핵심 사항만 소스코드로 예시한다.

전체 소스코드는 Github 에서 받아서 Project 에서 New Module을 생성하고 src 폴더를 덮어쓰기 해도 되고, 참고하면서 소스코드를 작성하는 것도 방법이다.

암호화함수를 모아둔 Class 는 오픈하지 않았으므로 전체적인 로직만 참고하면 도움될 것이다.

 

AddressRepository.java

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
 
import com.link2me.android.common.CipherUtils;
import com.link2me.android.rview_ex3.model.AddressResult;
import com.link2me.android.rview_ex3.network.RetrofitAdapter;
import com.link2me.android.rview_ex3.network.RetrofitService;
import com.link2me.android.rview_ex3.network.RetrofitURL;
 
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
 
public class AddressRepository {
    private RetrofitService service;
    private MutableLiveData<AddressResult> mutableLiveData;
 
    public AddressRepository() {
        mutableLiveData = new MutableLiveData<>();
        service = RetrofitAdapter.getClient(RetrofitURL.BaseUrl).create(RetrofitService.class);
    }
 
    public void addressDataRepo(String search) {
        String keyword = CipherUtils.encryptAES(CipherUtils.URLkey());
        service.GetAddrData(keyword, search).enqueue(new Callback<AddressResult>() {
            @Override
            public void onResponse(Call<AddressResult> call, Response<AddressResult> response) {
                mutableLiveData.postValue(response.body());
            }
 
            @Override
            public void onFailure(Call<AddressResult> call, Throwable t) {
                mutableLiveData.postValue(null);
            }
        });
    }
 
    public LiveData<AddressResult> getAddressResultLiveData(){
        return mutableLiveData;
    }
}
 

 

 

AddressViewModel.java

public class AddressViewModel extends ViewModel {
    private AddressRepository repository;
    private LiveData<AddressResult> _liveData;
 
    public AddressViewModel() {
    }
 
    public void getAllAddressData(String search){
        repository.addressDataRepo(search);
    }
 
    public LiveData<AddressResult> get_liveData(){
        if(_liveData == null){
            repository = new AddressRepository();
            _liveData = repository.getAddressResultLiveData();
        }
        return _liveData;
    }
}
 

 

 

AddressListAdapter.java

 
/***
 * https://www.youtube.com/watch?v=xPPMygGxiEo 동영상 시청하면 개념 이해하는데 엄청 도움됨
 */
 
// RecyclerView.Adapter 대신에 ListAdapter<T, VH> 를 상속한다.
// T는 리사이클러뷰가 화면에 표시할 데이터의 타입이다.
public class AddressListAdapter extends ListAdapter<Address_Item, AddressListAdapter.ViewHolder> {
    private final String TAG = this.getClass().getSimpleName();
    Context context;
 
    // 인터페이스 선언 -------------------------------------------------------------
    private OnItemClickListener mListener;
 
    public interface OnItemClickListener {
        void onItemClicked(Address_Item item, int position);
    }
 
    public void setOnItemClickListener(OnItemClickListener listener){
        mListener = listener;
    }
    // 인터페이스 ----------------------------------------------------------------
 
    public AddressListAdapter(Context context, @NonNull DiffUtil.ItemCallback<Address_Item> diffCallback) {
        super(diffCallback);
        this.context = context;
    }
 
    public void selectAll(int list_count){ // checkbox 전체 선택
        for(int i=0;i < list_count;i++){
            getItem(i).setCheckBoxState(true);
        }
        notifyDataSetChanged();
    }
    public void unselectall(int list_count){ // checkbox 전체 해제
        for(int i=0;i < list_count;i++){
            getItem(i).setCheckBoxState(false);
        }
        notifyDataSetChanged();
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemAddressBinding binding = ItemAddressBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }
 
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        // 실제로 데이터를 표시하는 부분
        //Address_Item currentItem = rvItemList.get(position);
        Address_Item currentItem = getItem(position);
        holder.bindItem(currentItem, position);
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder{
        ItemAddressBinding itemBinding;
 
        public ViewHolder(@NonNull ItemAddressBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item, int position){
            String photoURL = RetrofitURL.BaseUrl + "photos/" + item.getPhoto();
            if(photoURL.contains("null")){
                Glide.with(context).asBitmap().load(R.drawable.photo_base).into(itemBinding.profileImage);
            } else {
                Glide.with(context).asBitmap().load(photoURL).into(itemBinding.profileImage);
            }
            itemBinding.childName.setText(item.getUserNM());
            itemBinding.childMobileNO.setText(PhoneNumberUtils.formatNumber(item.getMobileNO()));
            itemBinding.childOfficeNO.setText(PhoneNumberUtils.formatNumber(item.getTelNO()));
 
            itemBinding.profileImage.setOnClickListener(view1 -> {
                if(mListener != null){
                    mListener.onItemClicked(item,position); // 클릭의 결과로 값을 전달
                }
            });
 
            if (isCheckFlag == false) {
                itemBinding.callBtn.setVisibility(View.VISIBLE);
                itemBinding.callBtn.setOnClickListener(view -> builder.show());
                itemBinding.listcellCheckbox.setVisibility(View.GONE);
                itemBinding.childLayout.setOnClickListener(view ->
                        Toast.makeText(context, "상세보기를 눌렀습니다 ===" + position + " , idx :" + item.getIdx(), Toast.LENGTH_SHORT).show());
 
                itemBinding.childLayout.setOnLongClickListener(v -> {
                    isCheckFlag = true;
                    constraintLayout.setVisibility(View.VISIBLE);
                    notifyDataSetChanged();
                    return true;
                });
            } else {
                itemBinding.callBtn.setVisibility(View.GONE);
                //convertView.setClickable(false);
                itemBinding.listcellCheckbox.setVisibility(View.VISIBLE);
                itemBinding.listcellCheckbox.setTag(position); // This line is important.
 
                // 체크 박스 클릭하면 CheckBoxState 에 반영한다. setOnCheckedChangeListener 대신 사용
                itemBinding.listcellCheckbox.setOnClickListener(v -> {
                    if(getItem(position).getCheckBoxState() == true){
                        getItem(position).setCheckBoxState(false);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    } else {
                        getItem(position).setCheckBoxState(true);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    }
                });
 
                itemBinding.childLayout.setOnClickListener(v -> {
                    if(itemBinding.listcellCheckbox.isChecked() == false){
                        itemBinding.listcellCheckbox.setChecked(true);
                        getItem(position).setCheckBoxState(true);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    } else {
                        itemBinding.listcellCheckbox.setChecked(false);
                        getItem(position).setCheckBoxState(false);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    }
                });
            }
 
            // 재사용 문제 해결
            if(getItem(position).getCheckBoxState() == true){
                itemBinding.listcellCheckbox.setChecked(true);
                Log.d("ReUse","position : " + position + " checkBoxState === " + getItem(position).getCheckBoxState());
            } else {
                itemBinding.listcellCheckbox.setChecked(false);
                Log.d("ReUse","position : "+position + " checkBoxState ===" + getItem(position).getCheckBoxState());
            }
        }
    }
 
}
 
 

 

 

AddressActivity.java

public class AddressActivity extends AppCompatActivity implements AddressListAdapter.OnItemClickListener {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;
    private ActivityAddressBinding binding;
 
    private AddressViewModel viewModel;
 
    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 전체 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private AddressListAdapter mAdapter;
 
    public static ConstraintLayout constraintLayout;
    public static boolean isCheckFlag = false;
    public CheckBox checkAll;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_address);
        binding = ActivityAddressBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
 
        mContext = AddressActivity.this;
        initView();
    }
 
    private void initView() {
        // 동기식 데이터 처리에서는 순서가 중요함.
        viewModelProvider();
        buildRecyclerView();
        getAddressList(); // 서버 데이터 가져오기
        setButtons();
    }
 
    private void buildRecyclerView() {
        mRecyclerView = binding.rvAddress;
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
 
        //mAdapter = new AddressViewAdapter(mContext,searchItemList); // 객체 생성
        mAdapter = new AddressListAdapter(mContext, Address_Item.itemCallback); // DiffUtil을 넣은 어댑터를 생성
        //mAdapter.submitList(searchItemList); // 서버에서 데이터를 가져온 이 후 코드에 추가하면 된다.
 
        DividerItemDecoration decoration = new DividerItemDecoration(mContext,manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
 
        mAdapter.setOnItemClickListener(this);
    }
 
    private void viewModelProvider() {
        // viewModel init
        viewModel = new ViewModelProvider(this).get(AddressViewModel.class);
 
        // viewModel observe
        viewModel.get_liveData().observe(thisnew Observer<AddressResult>() {
            @Override
            public void onChanged(AddressResult addressResult) {
                if(addressResult.getStatus().contains("success")){
                    addressItemList.clear(); // 서버에서 가져온 데이터 초기화 (전체 List)
                    searchItemList.clear(); // 서버에서 가져온 데이터 초기화 (검색 List)
 
                    for(Address_Item item : addressResult.getAddrinfo()){
                        addressItemList.add(item);
                        searchItemList.add(item);
                    }
 
                    Log.e(TAG, "viewModel observe");
 
                    mAdapter.submitList(searchItemList);
 
                } else {
                    Utils.showAlert(mContext,addressResult.getStatus(),addressResult.getMessage());
                }
            }
        });
 
    }
 
    private void getAddressList() {
        String search = "";
        viewModel.getAllAddressData(search);
    }
 
    private void setButtons(){
        isCheckFlag = false;
        constraintLayout = binding.listviewConstraint2;
 
        binding.search.setSubmitButtonEnabled(false);
        binding.search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                // 문자열 입력을 완료했을 때 문자열 반환
                // 쿼리가 비어있을 때에는 호출되지 않는다.
                //viewModel.getAllAddressData(query); // 서버 재접속 후 데이터 가져오기
                return false;
            }
 
            @Override
            public boolean onQueryTextChange(String newText) {
                // 문자열이 변할 때마다 바로바로 문자열 반환
                filter(newText); // 전체 데이터 가져온 후 로컬에서 검색 처리
//                if(newText.trim().length() == 0) {
//                    viewModel.getAllAddressData("");
//                }
                return true;
            }
        });
 
        if (isCheckFlag == false) {
            constraintLayout.setVisibility(View.GONE);
        } else if (isCheckFlag == true) {
            constraintLayout.setVisibility(View.VISIBLE);
        }
 
        // all checkbox
        checkAll = binding.lvCheckboxAll;
        binding.lvCheckboxAll.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (checkAll.isChecked() == true) {
                mAdapter.selectAll(searchItemList.size());
            } else {
                mAdapter.unselectall(searchItemList.size());
            }
        });
 
        binding.btnCancel.setOnClickListener(v -> {
            isCheckFlag = false;
            constraintLayout.setVisibility(View.GONE);
            mAdapter.unselectall(searchItemList.size());
            checkAll.setChecked(false); // 전체 선택 체크박스 해제
        });
    }
 
    // Filter Class
    public void filter(String charText) {
        charText = charText.toLowerCase(Locale.getDefault());
        searchItemList.clear();
        if (charText.length() == 0) {
            searchItemList.addAll(addressItemList);
        } else {
            for (Address_Item wp : addressItemList) {
                if(Utils.isNumber(charText)){ // 숫자여부 체크
                    if(wp.getMobileNO().contains(charText) || wp.getTelNO().contains(charText)){
                        // 휴대폰번호 또는 사무실번호에 숫자가 포함되어 있으면
                        searchItemList.add(wp);
                    }
                } else {
                    String iniName = HangulUtils.getHangulInitialSound(wp.getUserNM(), charText);
                    if (iniName.indexOf(charText) >= 0) { // 초성검색어가 있으면 해당 데이터 리스트에 추가
                        searchItemList.add(wp);
                    } else if (wp.getUserNM().toLowerCase(Locale.getDefault()).contains(charText)) {
                        searchItemList.add(wp);
                    }
                }
            }
        }
        mAdapter.notifyDataSetChanged();
    }
 
    @Override
    public void onItemClicked(Address_Item item, int position) {
        Toast.makeText(mContext, "position : "+position + " clieck item name : "+item.getUserNM(), Toast.LENGTH_SHORT).show();
    }
}
 

 

Github Source Code : https://github.com/jsk005/JavaProjects/tree/master/recyclerview_mvvm

 

GitHub - jsk005/JavaProjects: 자바 기능 테스트

자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

블로그 이미지

Link2Me

,
728x90

코틀린 기반으로 코드를 구현하면 쉽게 샘플 예제를 찾을 수 있다. 하지만 Java 코드 기반 자료는 찾기가 쉽지 않다.

제일 빠르게 찾는 방법은 유투브 동영상에서 검색하는 것이다.

https://www.youtube.com/watch?v=xPPMygGxiEo 

이 동영상 강좌 시청하면서 링크된 Github 샘플 자료를 참고하면 이해하는데 훨씬 빠를 것이다.

 

 

ListAdapter는 RecyclerView에 쓸 수 있는 어댑터이다.
기존의 어댑터와는 다르게 DiffUtil을 사용하여 비동기식 처리를 할 수 있다.
기존의 기본 어댑터는 mAdapter.notifyDataSetChanged(); 메소드를 사용하여 리스트의 변경처리를 했었는데, 
ListAdapter에서는 submitList(...) 를 사용하여 리스트가 변경되었음을 어댑터에게 알려줄 수 있다.

 

DiffUtil 클래스는 oldItem, newItem의 두 데이터셋을 비교하여 값이 변경된 부분만을 RecyclerView에게 알려줄 수 있다.

비교 결과, 다르다고 판단된 아이템 항목만 교체를 진행한다. 이것이 중요한 포인트이다.

 

아래 샘플 예제는 MVVM 패턴을 고려하여 작성한 코드는 아니며, 내용 숙지 차원에서 수정 부분 중심으로 적어둔다.

 

Java 코드로 구현한 Class

- 일단 코드가 참 길다.

- DiffUtil.ItemCallback 메소드 구현을 이곳에 하는 예제도 있고 RecyclerViewApdater 에 구현한 예제도 있다.

import android.graphics.Movie;
 
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
 
import java.util.Objects;
 
public class Address_Item {
    private String idx;
    private String userNM;
    private String mobileNO;
    private String telNO;
    private String photo;
    private Boolean checkBoxState;
 
    public Address_Item(String idx, String userNM, String mobileNO, String telNO, String photo, Boolean checkBoxState) {
        this.idx = idx;
        this.userNM = userNM;
        this.mobileNO = mobileNO;
        this.telNO = telNO;
        this.photo = photo;
        this.checkBoxState = checkBoxState;
    }
 
    public String getIdx() {
        return idx;
    }
 
    public void setIdx(String idx) {
        this.idx = idx;
    }
 
    public String getUserNM() {
        return userNM;
    }
 
    public void setUserNM(String userNM) {
        this.userNM = userNM;
    }
 
    public String getMobileNO() {
        return mobileNO;
    }
 
    public void setMobileNO(String mobileNO) {
        this.mobileNO = mobileNO;
    }
 
    public String getTelNO() {
        return telNO;
    }
 
    public void setTelNO(String telNO) {
        this.telNO = telNO;
    }
 
    public String getPhoto() {
        return photo;
    }
 
    public void setPhoto(String photo) {
        this.photo = photo;
    }
 
    public Boolean getCheckBoxState() {
        return checkBoxState;
    }
 
    public void setCheckBoxState(Boolean checkBoxState) {
        this.checkBoxState = checkBoxState;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address_Item that = (Address_Item) o;
        return Objects.equals(idx, that.idx) && Objects.equals(userNM, that.userNM) && Objects.equals(mobileNO, that.mobileNO) && Objects.equals(telNO, that.telNO) && Objects.equals(photo, that.photo) && Objects.equals(checkBoxState, that.checkBoxState);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(idx, userNM, mobileNO, telNO, photo, checkBoxState);
    }
 
    public static DiffUtil.ItemCallback<Address_Item> itemCallback = new DiffUtil.ItemCallback<Address_Item>() {
        @Override
        public boolean areItemsTheSame(@NonNull Address_Item oldItem, @NonNull Address_Item newItem) {
            return oldItem.getIdx().equals(newItem.getIdx());
        }
 
        @Override
        public boolean areContentsTheSame(@NonNull Address_Item oldItem, @NonNull Address_Item newItem) {
            return oldItem.equals(newItem);
        }
    };
}
 

 

코드가 너무 긴 관계로 Java 에서 코틀린으로 코드를 변환시켰다.

그 다음에 var 변수 초기화 부분의 코드를 약간 수정했다.

이것은 Java 로 구현된 코드와 혼용 사용하는 Class 이다.

import android.os.Parcelable
import androidx.recyclerview.widget.DiffUtil
import kotlinx.parcelize.Parcelize
import java.util.*
 
@Parcelize
class Address_Item(
    // 결과를 받을 모델 (ArrayList 에 저장하므로 val 로 선언하면 안된다)
    // 서버 SQL의 칼럼명과 일치하게 작성해야 한다.
    var idx: String="",
    var userNM: String="",
    var mobileNO: String?=null,
    var telNO: String?=null,
    var photo: String?=null,
    var checkBoxState: Boolean
) : Parcelable {
    override fun equals(o: Any?): Boolean {
        if (this === o) return true
        if (o == null || javaClass != o.javaClass) return false
        val that = o as Address_Item
        return idx == that.idx && userNM == that.userNM && mobileNO == that.mobileNO && telNO == that.telNO && photo == that.photo && checkBoxState == that.checkBoxState
    }
 
    override fun hashCode(): Int {
        return Objects.hash(idx, userNM, mobileNO, telNO, photo, checkBoxState)
    }
 
    companion object {
        @JvmField
        var itemCallback: DiffUtil.ItemCallback<Address_Item> =
            object : DiffUtil.ItemCallback<Address_Item>() {
                override fun areItemsTheSame(oldItem: Address_Item, newItem: Address_Item): Boolean {
                    return oldItem.idx == newItem.idx
                }
 
                override fun areContentsTheSame(oldItem: Address_Item, newItem: Address_Item): Boolean {
                    return oldItem == newItem
                }
            }
    }
}

 

 

AddressActivity.java

- 기존 파일에서 객체를 생성하는 코드는 주석처리했다.

- Adpater 로 데이터를 전달하는 부분은 mAdpater.submitList(전달변수);

  이 코드는 서버에서 자료를 가져온 후 데이터를 넘기는 곳에 적어주면 된다.

   private void buildRecyclerView() {
        mRecyclerView = binding.rvAddress;
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
 
        //mAdapter = new AddressViewAdapter(mContext,searchItemList); // 객체 생성
        mAdapter = new AddressListAdapter(mContext, Address_Item.itemCallback); // DiffUtil을 넣은 어댑터를 생성
        //mAdapter.submitList(searchItemList); // 서버에서 데이터를 가져온 이 후 코드에 추가하면 된다.
 
        DividerItemDecoration decoration = new DividerItemDecoration(mContext,manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
 
        mAdapter.setOnItemClickListener(this);
    }

 

 

AddressListAdapter.java

- 기존 RecyclerView.Adapter 대신에 ListAdapter<T, VH> 를 상속한다.

- RecyclerView 가 화면에 표시할 데이터 타입 Address_Item 을 추가한다.

- 이렇게 변경하고 나면 생성자 부분이 에러가 발생한다. 자동으로 추가하면 protected 생성되는데 public 으로 변경하고 DiffUtil.ItemCallback 코드는 이곳에서 추가 구현해도 되고, Address_Item Class 에 추가 구현한 것을 사용해도 된다.

- rvItemList.get 대신에 getItem 으로 변경한다.

- getItemCount() 메소드는 삭제한다.

import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
 
// RecyclerView.Adapter 대신에 ListAdapter<T, VH> 를 상속한다.
// T는 리사이클러뷰가 화면에 표시할 데이터의 타입이다.
public class AddressListAdapter extends ListAdapter<Address_Item, AddressListAdapter.ViewHolder> {
    private final String TAG = this.getClass().getSimpleName();
    Context context;
 
    public AddressListAdapter(Context context, @NonNull DiffUtil.ItemCallback<Address_Item> diffCallback) {
        super(diffCallback);
        this.context = context;
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemAddressBinding binding = ItemAddressBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }
 
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        // 실제로 데이터를 표시하는 부분
        //Address_Item currentItem = rvItemList.get(position);
        Address_Item currentItem = getItem(position);
        holder.bindItem(currentItem, position);
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder{
        ItemAddressBinding itemBinding;
 
        public ViewHolder(@NonNull ItemAddressBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item, int position){
        }
    }
 
}
 

 

 

 

블로그 이미지

Link2Me

,
728x90

RecyclerView가 보여주는 ITEM VIEW 들을 만들어주는 adapter에서 사용하는 ViewHolder의 아이템뷰에 view binding을 적용하는 것이다.

 

테스트를 하다보니 RecyclerView 를 적용한 Adapter 와 Activity 모두 view binding 을 적용해야 이미지가 정상적으로 보이는 것을 확인할 수 있었다.

 

변경되는 사항

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 새로운 뷰를 만든다.
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.address_item,parent,false);
        ViewHolder viewHolder = new ViewHolder(itemView);
        return viewHolder;
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        AddressItemBinding binding = AddressItemBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }

 

 

    public class ViewHolder extends RecyclerView.ViewHolder  {
        View layout;
        ImageView photoImg;
        TextView tv_name;
        TextView tv_mobileNO;
        TextView tv_telNO;
        ImageView call_btn;
        CheckBox cbSelect;
 
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            layout = itemView.findViewById(R.id.child_layout);
            photoImg = itemView.findViewById(R.id.profile_Image);
            tv_name = itemView.findViewById(R.id.child_name);
            tv_mobileNO = itemView.findViewById(R.id.child_mobileNO);
            tv_telNO = itemView.findViewById(R.id.child_telNO);
            call_btn = itemView.findViewById(R.id.child_Btn);
            cbSelect = itemView.findViewById(R.id.list_cell_checkbox);
        }
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder  {
        AddressItemBinding itemBinding;
 
        public ViewHolder(@NonNull AddressItemBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item){
            if(!TextUtils.isEmpty(item.getPhoto())){
                Glide.with(mContext).load(item.getPhoto()).into(itemBinding.profileImage);
            }
            itemBinding.childName.setText(item.getUserNM());
            itemBinding.childMobileNO.setText(PhoneNumberUtils.formatNumber(item.getMobileNO()));
            itemBinding.childTelNO.setText(PhoneNumberUtils.formatNumber(item.getTelNO()));
 
            itemBinding.childBtn.setOnClickListener(v -> {
 
            });
            itemBinding.listCellCheckbox.setChecked(item.getCheckBoxState());
        }
    }
 

 

View Binding을 적용한 코드

 

package com.link2me.android.sample.adapter;
 
import static com.link2me.android.sample.main.AddrBindActivity.constraintLayout;
import static com.link2me.android.sample.main.AddrBindActivity.isCheckFlag;
 
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
 
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
 
import com.bumptech.glide.Glide;
import com.link2me.android.sample.R;
import com.link2me.android.sample.databinding.AddressItemBinding;
import com.link2me.android.sample.model.Address_Item;
import com.link2me.android.sample.network.RetrofitUrl;
 
import java.util.ArrayList;
 
public class BindListViewAdapter extends RecyclerView.Adapter<BindListViewAdapter.ViewHolder> {
    private final String TAG = this.getClass().getSimpleName();
    Context context;
    private ArrayList<Address_Item> rvItemList;
 
    // 인터페이스 선언 -------------------------------------------------------------
    private OnItemClickListener mListener;
 
    public interface OnItemClickListener {
        void onItemClicked(View view, Address_Item item, int position);
    }
 
    public void setOnItemSelectClickListener(OnItemClickListener listener){
        mListener = listener;
    }
    // 인터페이스 ----------------------------------------------------------------
 
    public BindListViewAdapter(Context context, ArrayList<Address_Item> items) {
        this.context = context;
        rvItemList = items;
    }
 
    public void selectAll(){ // checkbox 전체 선택
        for(int i=0;i < rvItemList.size();i++){
            rvItemList.get(i).setCheckBoxState(true);
        }
        notifyDataSetChanged();
    }
    public void unselectall(){ // checkbox 전체 해제
        for(int i=0;i < rvItemList.size();i++){
            rvItemList.get(i).setCheckBoxState(false);
        }
        notifyDataSetChanged();
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 새로운 뷰를 만든다.
        AddressItemBinding binding = AddressItemBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }
 
    @Override
    public void onBindViewHolder(@NonNull BindListViewAdapter.ViewHolder holder, int position) {
        holder.bindItem(rvItemList.get(position), position);
    }
 
    @Override
    public int getItemCount() {
        return rvItemList.size();
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder  {
        AddressItemBinding itemBinding;
 
        public ViewHolder(@NonNull AddressItemBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item, int position){
            String photoURL = RetrofitUrl.BASE_URL + "photos/" + item.getPhoto();
            if(photoURL.contains("null")){
                Glide.with(context).asBitmap().load(R.drawable.photo_base).into(itemBinding.profileImage);
            } else {
                Glide.with(context).asBitmap().load(photoURL).into(itemBinding.profileImage);
            }
            itemBinding.childName.setText(item.getUserNM());
            itemBinding.childMobileNO.setText(PhoneNumberUtils.formatNumber(item.getMobileNO()));
            itemBinding.childOfficeNO.setText(PhoneNumberUtils.formatNumber(item.getTelNO()));
 
            itemBinding.profileImage.setOnClickListener(view1 -> {
                if(mListener != null){
                    mListener.onItemClicked(view1,item,position);
                    // 클릭의 결과로 값을 전달
                    Log.e(TAG,"item clicked : "+position);
                }
            });
 
            final String[] items ={"휴대폰 전화걸기","사무실전화 걸기"};
            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("해당작업을 선택하세요");
            builder.setItems(items, (dialog, which) -> {
                Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show();
                switch (which){
                    case 0:
                        if(item.getMobileNO().length() ==0){
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
 
                        AlertDialog dialog1 = new AlertDialog.Builder(context)
                                .setTitle(item.getUserNM())
                                .setMessage(PhoneNumberUtils.formatNumber(item.getMobileNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        (dialog23, which13) -> {
 
                                            Intent intent = new Intent(Intent.ACTION_CALL,
                                                    Uri.parse("tel:" + PhoneNumberUtils.formatNumber(item.getMobileNO())));
                                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                            context.startActivity(intent);
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        (dialog22, which12) -> dialog22.dismiss()).create();
                        dialog1.show();
                        break;
                    case 1:
                        if(item.getTelNO().length() ==0){
                            Toast.makeText(context, "전화걸 사무실 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog2 = new AlertDialog.Builder(context)
                                .setTitle(item.getUserNM())
                                .setMessage(PhoneNumberUtils.formatNumber(item.getTelNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        (dialog3, which1) -> {
 
                                            Intent intent = new Intent(Intent.ACTION_CALL,
                                                    Uri.parse("tel:" + PhoneNumberUtils.formatNumber(item.getTelNO())));
                                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                            context.startActivity(intent);
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        (dialog32, which14) -> dialog32.dismiss()).create();
                        dialog2.show();
                        break;
 
                }
            });
            builder.create();
 
            itemBinding.callBtn.setOnClickListener(v -> builder.show());
 
 
            if (isCheckFlag == false) {
                itemBinding.callBtn.setVisibility(View.VISIBLE);
                itemBinding.callBtn.setOnClickListener(view -> builder.show());
                itemBinding.listcellCheckbox.setVisibility(View.GONE);
                itemBinding.childLayout.setOnClickListener(view ->
                        Toast.makeText(context, "상세보기를 눌렀습니다 ===" + item.getIdx(), Toast.LENGTH_SHORT).show());
 
                itemBinding.childLayout.setOnLongClickListener(v -> {
                    isCheckFlag = true;
                    constraintLayout.setVisibility(View.VISIBLE);
                    notifyDataSetChanged();
                    return true;
                });
            } else {
                itemBinding.callBtn.setVisibility(View.GONE);
                //convertView.setClickable(false);
                itemBinding.listcellCheckbox.setVisibility(View.VISIBLE);
                itemBinding.listcellCheckbox.setTag(position); // This line is important.
 
                // 체크 박스 클릭하면 CheckBoxState 에 반영한다. setOnCheckedChangeListener 대신 사용
                itemBinding.listcellCheckbox.setOnClickListener(v -> {
                    if(rvItemList.get(position).getCheckBoxState() == true){
                        rvItemList.get(position).setCheckBoxState(false);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    } else {
                        rvItemList.get(position).setCheckBoxState(true);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    }
                });
 
                itemBinding.childLayout.setOnClickListener(v -> {
                    if(itemBinding.listcellCheckbox.isChecked() == false){
                        itemBinding.listcellCheckbox.setChecked(true);
                        rvItemList.get(position).setCheckBoxState(true);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    } else {
                        itemBinding.listcellCheckbox.setChecked(false);
                        rvItemList.get(position).setCheckBoxState(false);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    }
                });
            }
 
            // 재사용 문제 해결
            if(rvItemList.get(position).getCheckBoxState() == true){
                itemBinding.listcellCheckbox.setChecked(true);
                Log.d("ReUse","position : " + position + " checkBoxState === " + rvItemList.get(position).getCheckBoxState());
            } else {
                itemBinding.listcellCheckbox.setChecked(false);
                Log.d("ReUse","position : "+position + " checkBoxState ===" + rvItemList.get(position).getCheckBoxState());
            }
        }
    }
 
}
 
 
 
 
 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

https://www.geeksforgeeks.org/how-to-create-expandable-recyclerview-items-in-android-using-kotlin/

에 있는 소스를 수정/보완했다.

 

RecyclerView 에서 해당 항목을 누르면 세부 내용을 보였다 사라졌다 하는 기능을 구현하는 걸 Expandable RecyclerView 라고 한다.

 

앱 build.gradle

- 다른 코드에서 사용하기 위해 dependancies 를 더 추가했다.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.link2me.expandablerv"
        minSdkVersion 22
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

    }

    buildFeatures {
        viewBinding true
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

    // ViewPager2
    implementation 'androidx.viewpager2:viewpager2:1.0.0'

    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'  // 레이아웃으로 사용할 CardView
    implementation 'androidx.recyclerview:recyclerview:1.1.0'

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'

    // 이미지 출력용 Glide
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}

 

레이아웃 만들기

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="#F5F8FD"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/single_item"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

single_item.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/card_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="3dp"
    android:layout_marginEnd="3dp"
    android:layout_marginBottom="3dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/tv_main_layout"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:orientation="horizontal"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            tools:ignore="MissingConstraints">
            <!--Text view -->
            <TextView
                android:id="@+id/tv_main_name"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginStart="20dp"
                android:layout_marginTop="10dp"
                android:text="Language"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:textStyle="bold"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <View
                android:layout_width="15dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"/>

            <ImageButton
                android:id="@+id/viewMoreBtn"
                android:tint="#666666"
                android:src="@drawable/ic_arrow_drop_down_24"
                android:background="?attr/selectableItemBackgroundBorderless"
                android:layout_width="?attr/actionBarSize"
                android:layout_height="?attr/actionBarSize" />
        </LinearLayout>

        <!--"expanded_view" -->
        <LinearLayout
            android:id="@+id/expanded_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="10dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_main_layout">

            <TextView
                android:id="@+id/tv_description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:text="Description Text"
                android:textSize="18sp" />
        </LinearLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

 

 

MainActivity.kt

class MainActivity : AppCompatActivity() {
    // https://www.geeksforgeeks.org/how-to-create-expandable-recyclerview-items-in-android-using-kotlin/ 사이트 소스를 수정

    // view binding for the activity
    private var _binding: ActivityMainBinding? = null
    private val binding get() = _binding!!

    // get reference to the adapter class
    private var languageList = ArrayList<DataItem>()
    private lateinit var expandableAdapter: ExpandableAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // define layout manager for the Recycler view
        binding.rvList.layoutManager = LinearLayoutManager(this)
        // attach adapter to the recyclerview
        expandableAdapter = ExpandableAdapter(languageList)

        getData()

        binding.rvList.adapter = expandableAdapter
    }

    private fun getData() {
        // 서버에서 가져온 데이터라고 가정한다.
        // create new objects and add some row data
        val language1 = DataItem(
            "Java",
            "Java is an Object Oriented Programming language." +
                    " Java is used in all kind of applications like Mobile Applications (Android is Java based), " +
                    "desktop applications, web applications, client server applications, enterprise applications and many more. ",
            false
        )
        val language2 = DataItem(
            "Kotlin",
            "Kotlin is a statically typed, general-purpose programming language" +
                    " developed by JetBrains, that has built world-class IDEs like IntelliJ IDEA, PhpStorm, Appcode, etc.",
            false
        )
        val language3 = DataItem(
            "Python",
            "Python is a high-level, general-purpose and a very popular programming language." +
                    " Python programming language (latest Python 3) is being used in web development, Machine Learning applications, " +
                    "along with all cutting edge technology in Software Industry.",
            false
        )
        val language4 = DataItem(
            "CPP",
            "C++ is a general purpose programming language and widely used now a days for " +
                    "competitive programming. It has imperative, object-oriented and generic programming features. ",
            false
        )

        // add items to list
        languageList.add(language1)
        languageList.add(language2)
        languageList.add(language3)
        languageList.add(language4)

        expandableAdapter.notifyDataSetChanged()

    }

    // on destroy of view make the binding reference to null
    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

 

DataItem.kt

class DataItem(
    val name : String ="",
    val description : String= "",
    var expand : Boolean = false
)

 

ExpandableAdapter.kt

이미지가 펼쳐졌을 때, Up/Down 화살표가 변경되는 걸 추가하고자 한다면...

if(this.expand) binding.viewMoreBtn.setImageResource(R.drawable.ic_arrow_drop_up_24) else binding.viewMoreBtn.setImageResource(R.drawable.ic_arrow_drop_down_24)

를 추가하면 된다.

class ExpandableAdapter(private var itemList: List<DataItem>) : RecyclerView.Adapter<ExpandableAdapter.ViewHolder>() {

    // create an inner class with name ViewHolder
    // It takes a view argument, in which pass the generated class of single_item.xml
    // ie SingleItemBinding and in the RecyclerView.ViewHolder(binding.root) pass it like this
    inner class ViewHolder(val binding: SingleItemBinding) : RecyclerView.ViewHolder(binding.root)

    // inside the onCreateViewHolder inflate the view of SingleItemBinding
    // and return new ViewHolder object containing this layout
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = SingleItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        with(holder){
            with(itemList[position]){
                // set name of the language from the list
                binding.tvMainName.text = this.name
                binding.tvDescription.text = this.description
                binding.expandedView.visibility = if (this.expand) View.VISIBLE else View.GONE
                if(this.expand) binding.viewMoreBtn.setImageResource(R.drawable.ic_arrow_drop_up_24) else binding.viewMoreBtn.setImageResource(R.drawable.ic_arrow_drop_down_24)
                binding.cardLayout.setOnClickListener {
                    this.expand = !this.expand
                    notifyDataSetChanged()
                }
            }
        }
    }

    override fun getItemCount(): Int = itemList.size
    
}

 

GitHub 에 올린 전체 소스 코드

https://github.com/jsk005/KotlinProjects/tree/master/expandablerv

블로그 이미지

Link2Me

,
728x90

LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
// Set the layout manager to your recyclerview
recyclerView.setLayoutManager(mLayoutManager);


출처 : https://freakycoder.com/android-notes-46-how-to-reverse-recyclerview-by-adding-items-f32db1e36c51

블로그 이미지

Link2Me

,
728x90

RecyclerviewAdapter 에서 Interface로 Activity 로 값을 넘기는 방법에 대한 코드와 Checkbox 에서 전체 선택/해제를 위해 별도의 RelativeLayout 으로 전체선택, 취소, 전송 등의 버튼을 처리하도록 한다.

구현은 전체를 다 구현한 것이 아니라 핵심사항만 구현했지만, 이 정도로도 좋은 코드라고 본다.


MainActivity.java

public class MainActivity extends AppCompatActivity implements RecyclerViewAdapter.OnItemClickListener {
    private final String TAG = this.getClass().getSimpleName();
    Context context;

    public ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 원본 데이터 리스트
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;
    private SearchView editsearch;

    RemoteService remoteService;

    public static RelativeLayout relative2;
    public static boolean isCheckFlag = false;
    public CheckBox checkAll;

    private BackPressHandler backPressHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = MainActivity.this;
        backPressHandler = new BackPressHandler(this);

        initView();
    }

    private void initView() {
        createAddressList(); // 서버 데이터 가져오기
        buildRecyclerView();
        setButtons();
    }

    private void setButtons(){
        isCheckFlag = false;
        // Relative layout 정의
        relative2 = findViewById(R.id.list_view_relative2);

        if (isCheckFlag == false) {
            relative2.setVisibility(View.GONE);
        } else if (isCheckFlag == true) {
            relative2.setVisibility(View.VISIBLE);
        }

        // all checkbox
        checkAll = findViewById(R.id.lv_checkbox_all);
        checkAll.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (checkAll.isChecked() == true) {
                mAdapter.selectAll();
                mAdapter.notifyDataSetChanged();
            } else {
                mAdapter.unselectall();
                mAdapter.notifyDataSetChanged();
            }
        });

        Button cancel = findViewById(R.id.btn_cancel);
        cancel.setOnClickListener(v -> {
            isCheckFlag = false;
            relative2.setVisibility(View.GONE);
            mAdapter.unselectall();
            checkAll.setChecked(false); // 전체 선택 체크박스 해제
            mAdapter.notifyDataSetChanged();
        });

        Button send = findViewById(R.id.btn_send);
        final String[] listview_items = {"그룹문자 보내기", "연락처 저장"};
        final AlertDialog.Builder items_builder = new AlertDialog.Builder(MainActivity.this);
        items_builder.setTitle("해당 작업을 선택하세요");
        items_builder.setItems(listview_items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                    case 0:
                        Toast.makeText(getApplicationContext(), "그룹문자 보내기 구현하세요.", Toast.LENGTH_SHORT).show();
                        break;
                    case 1:
                        Toast.makeText(getApplicationContext(), "연락처 저장을 구현하세요.", Toast.LENGTH_SHORT).show();
                        break;
                }
            }
        });
        items_builder.create();

        send.setOnClickListener(v ->

                items_builder.show());

    }

    private void buildRecyclerView(){
        mRecyclerView = findViewById(R.id.address_listview);
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(context);
        mAdapter = new RecyclerViewAdapter(context,addressItemList);

        DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);

        mAdapter.setOnItemSelectClickListener(this);
    }

    private void createAddressList() {
         // 서버 데이터 가져오기 구현 메서드는 https://link2me.tistory.com/1850 참조
    }

    @Override
    public void onBackPressed() {
        backPressHandler.onBackPressed();
    }

    @Override
    public void onItemClicked(View view, Address_Item item, int position) {
        Toast.makeText(context, "position : "+position + " clieck item name : "+item.getUserNM(), Toast.LENGTH_SHORT).show();
    }
}


RecyclerViewAdapter.java

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
    private final String TAG = this.getClass().getSimpleName();
    Context context;
    private List<Address_Item> rvItemList;

    // 인터페이스 선언 -------------------------------------------------------------
    private OnItemClickListener mListener;

    public interface OnItemClickListener {
        void onItemClicked(View view, Address_Item item, int position);
    }

    public void setOnItemSelectClickListener(OnItemClickListener listener){
        mListener = listener;
    }
    // 인터페이스 ----------------------------------------------------------------

    public class ViewHolder extends RecyclerView.ViewHolder{
        LinearLayout child_layout;
        ImageView photo_Image;
        TextView tv_name;
        TextView tv_mobileNO;
        TextView tv_officeNO;
        ImageView call_btn;
        CheckBox cbSelect;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            child_layout = itemView.findViewById(R.id.child_layout);
            photo_Image = itemView.findViewById(R.id.profile_Image);
            tv_name = itemView.findViewById(R.id.child_name);
            tv_mobileNO = itemView.findViewById(R.id.child_mobileNO);
            tv_officeNO = itemView.findViewById(R.id.child_officeNO);
            call_btn = itemView.findViewById(R.id.call_btn);
            cbSelect = itemView.findViewById(R.id.listcell_checkbox);
        }
    }

    public RecyclerViewAdapter(Context context, List<Address_Item> itemList) {
        this.context = context;
        rvItemList = itemList;
    }

    public void selectAll(){ // checkbox 전체 선택
        for(int i=0;i < rvItemList.size();i++){
            rvItemList.get(i).setCheckBoxState(true);
        }
        notifyDataSetChanged();
    }
    public void unselectall(){ // checkbox 전체 해제
        for(int i=0;i < rvItemList.size();i++){
            rvItemList.get(i).setCheckBoxState(false);
        }
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.address_item,parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Log.d(TAG,"onBindViewHolder: called."); // 실제로 데이터를 표시하는 부분
        Address_Item currentItem = rvItemList.get(position);

        String photoURL = Value.IPADDRESS + "photos/" + currentItem.getPhoto();
        if(photoURL.contains("null")){
            Glide.with(context).asBitmap().load(R.drawable.photo_base).into(holder.photo_Image);
        } else {
            Glide.with(context).asBitmap().load(photoURL).into(holder.photo_Image);
        }
        holder.tv_name.setText(currentItem.getUserNM());
        holder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(currentItem.getMobileNO()));
        holder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(currentItem.getTelNO()));

        holder.photo_Image.setOnClickListener(view1 -> {
            if(mListener != null){
                mListener.onItemClicked(view1,currentItem,position);
            }
        });

        holder.call_btn.setOnClickListener(view -> {
            Toast.makeText(context, "전화걸기 또는 문자 보내기를 할 수 있어요.", Toast.LENGTH_SHORT).show();
        });

        if (isCheckFlag == false) {
            holder.call_btn.setVisibility(View.VISIBLE);
            holder.call_btn.setOnClickListener(view -> builder.show());
            holder.cbSelect.setVisibility(View.GONE);
            holder.child_layout.setOnClickListener(view ->
                    Toast.makeText(context, "상세보기를 눌렀습니다 ===" + currentItem.getUid(), Toast.LENGTH_SHORT).show());

            holder.child_layout.setOnLongClickListener(v -> {
                isCheckFlag = true;
                relative2.setVisibility(View.VISIBLE);
                notifyDataSetChanged();
                return true;
            });
        } else {
            holder.call_btn.setVisibility(View.GONE);
            holder.cbSelect.setVisibility(View.VISIBLE);
            holder.cbSelect.setTag(position); // This line is important.

            // 체크 박스 클릭하면 CheckBoxState 에 반영한다. setOnCheckedChangeListener 대신 사용
            holder.cbSelect.setOnClickListener(v -> {
                if(rvItemList.get(position).getCheckBoxState() == true){
                    rvItemList.get(position).setCheckBoxState(false);
                } else {
                    rvItemList.get(position).setCheckBoxState(true);
                }
            });

            holder.child_layout.setOnClickListener(v -> {
                if(holder.cbSelect.isChecked() == false){
                    holder.cbSelect.setChecked(true);
                    rvItemList.get(position).setCheckBoxState(true);
                } else {
                    holder.cbSelect.setChecked(false);
                    rvItemList.get(position).setCheckBoxState(false);
                }
            });
        }

        // 재사용 문제 해결
        if(rvItemList.get(position).getCheckBoxState() == true){
            holder.cbSelect.setChecked(true);          
        } else {
            holder.cbSelect.setChecked(false);          
        }

    }

    @Override
    public int getItemCount() {
        return rvItemList.size();
    }

}


Address_Item.kt

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Address_Item (
    // 결과를 받을 모델 (ArrayList 에 저장하므로 val 로 선언하면 안된다)
    var uid: String="",
    var userNM: String="",
    var mobileNO: String?=null,
    var telNO: String?=null,
    var photo: String?=null,
    var checkBoxState: Boolean
): Parcelable


address_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="1dp"
    app:cardCornerRadius="1dp">

    <LinearLayout
        android:id="@+id/child_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">

        <ImageView
            android:id="@+id/profile_Image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_gravity="center"
            android:layout_weight="1"
            android:src="@mipmap/ic_launcher" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_weight="4"
            android:orientation="vertical">

            <TextView
                android:id="@+id/child_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingBottom="2dip"
                android:text="이름"
                android:textSize="20sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/child_mobileNO"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="left"
                android:text="휴대폰번호"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/child_officeNO"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="left"
                android:text="사무실번호"
                android:textStyle="bold" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/call_btn"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_gravity="center"
                android:gravity="center_horizontal|center_vertical"
                android:src="@drawable/btn_phone" />

        </LinearLayout>

        <CheckBox
            android:id="@+id/listcell_checkbox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:focusable="false" />

    </LinearLayout>
</androidx.cardview.widget.CardView>



블로그 이미지

Link2Me

,
728x90

RecyclerView 기본 개념을 자세히 다루지는 않는다.



    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
}


dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'  // 레이아웃으로 사용할 CardView
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

최신(2020.12월) 기준으로는 implementation 'androidx.recyclerview:recyclerview:1.1.0' 를 추가하면 된다.

아래 이미지의 import는 이전 자료 기준이라고 보면 된다.


Android Studio에서 RecyclerViewAdapter 를 쉽게 만드는 방법을 유투브 동영상을 보면서 알게되어 적어둔다.

Alt + Eneter 키를 눌러서 자동완성으로 만드는 방법이다.

RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.Holder> 처럼 파란색상으로 된 클래스명은 동일하게 적어준다.

가장 먼저 Holder 클래스를 추가하고 나서 Implement methods 를 추가추고 번호 순서대로 하면 쉽게 코드가 자동 완성된다.





이렇게 하고 나면

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.Holder> {
    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }

    public class Holder extends RecyclerView.ViewHolder {
        public Holder(View itemView) {
            super(itemView);
        }
    }
}
 


모습으로 코드가 만들어진다.

여기에 실제 추가할 코드를 반영하여 작성한다.

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.Holder> {
    Context mContext;
    private ArrayList<MessageData> mMessageData;

    public RecyclerViewAdapter(Context context, ArrayList<MessageData> arrayList) {
        mContext = context;
        mMessageData = arrayList;
    }

    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new Holder(LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_layout,parent,false));
    }

    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {
        MessageData data = mMessageData.get(position);
        holder.heading.setText(data.getHeading());
        holder.message.setText(data.getMessage());
    }

    @Override
    public int getItemCount() {
        return mMessageData.size();
    }

    public class Holder extends RecyclerView.ViewHolder {
        TextView heading;
        TextView message;
        public Holder(View itemView) {
            super(itemView);
            heading = itemView.findViewById(R.id.heading);
            message = itemView.findViewById(R.id.messageBody);
        }
    }
}
 


Acitivy.java 에서 구현할 코드

RecyclerView 관련 코드만 적었다.

public class MainActivity extends AppCompatActivity {
    Context context;
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;
    private ArrayList<MessageData> mMessageData = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this.getBaseContext();

        initView();
    }

    private void initView(){
        mRecyclerView = findViewById(R.id.messagelist);
        mAdapter = new RecyclerViewAdapter(this,mMessageData);
        LinearLayoutManager manager = new LinearLayoutManager(this);
        DividerItemDecoration decoration = new DividerItemDecoration(this, manager.getOrientation());

        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
    }
}
 



블로그 이미지

Link2Me

,
728x90

recyclerview with multiple view types example 로 검색하면 검색되는 게시글이 https://hashcode.co.kr/questions/561/recyclerview%EC%97%90-%EC%97%AC%EB%9F%AC%EA%B0%9C%EC%9D%98-%EB%B7%B0%ED%83%80%EC%9E%85%EC%9D%84-%EB%84%A3%EC%9D%84-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C%EC%9A%94 로 나온다.

이와 비슷한 답변이 여기저기 검색된다. 나같은 초보한테는 전혀 도움이 안된다.

완성된 예제 코드를 봐야 약간 활용할 수 있게 된다.


https://www.loopwiki.com/ui-ux-design/recyclerview-with-header-and-footer-android-example/


http://www.sunilandroid.com/2016/11/multiple-view-type-in-recyclerview-in.html


https://guides.codepath.com/android/Heterogenous-Layouts-inside-RecyclerView


직접 테스트해본 것은 아니지만 괜찮은 코드로 보인다.


http://www.codexpedia.com/android/android-recyclerview-with-multiple-different-layouts/

이 게시글을 참조하고 나서 원하는 결과를 구현할 수 있었다.


코드를 구현하면서 Type_1, Type_2 가 서로 내용이 다르고, 서버에서 가져올 데이터의 코드도 서로 상이하여 고민이 많았다.

하지만 이런 코드를 검색하면서 ArrayList 는 1개로 통일하여 설계를 하고 Class 를 만들어야 한다는 점이다.

서버에서 가져올 코드에서 최대한 일치할 수 있는 것을 일치하고 없는 칼럼은 공백으로 저장을 하더라도 하나의 규격으로 Class를 만들어서 활용하면 View Type 를 서로 다르게 보여주는 것이 가능하다.


테스트에 사용된 dependencies 는 아래와 같다.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation 'com.loopj.android:android-async-http:1.4.9' // 서버와의 통신 라이브러리
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    implementation 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    implementation "com.android.support:cardview-v7:24.2.0"
}  ===> Android Studio 3.1.3


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.loopj.android:android-async-http:1.4.9' // 서버와의 통신 라이브러리
    compile 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    compile 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    compile "com.android.support:cardview-v7:24.2.0"
===> Android Studio 2.3.3


블로그 이미지

Link2Me

,
728x90

CardView를 사용하는 가장 중요한 이유는 깔끔하고 이뻐서다!

안드로이드 CardView 는 support.v7.widget 에 속한 라이브러리로 SDK 21버전부터 CardView가 추가되었다.

Android RecyclerView 를 사용하면서 CardView 를 같이 사용 할 수 있다.

하나의 RecyclerView 내부에 CardView가 들어가 있는 형태라고 보면 된다.


기본적인 설명은 https://www.journaldev.com/10024/android-recyclerview-android-cardview-example-tutorial 사이트를 참조하면 도움된다.


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation 'com.loopj.android:android-async-http:1.4.9' // 서버와의 통신 처리
    implementation 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    implementation 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    implementation "com.android.support:cardview-v7:24.2.0"
}


android:layout_margin="1dp" 로 했더니 깔끔하지 않아서 android:layout_marginBottom="1dp" 로 처리

cardCornerRadius : 레이아웃에 모서리 반지름 설정

cardBackgroundColor : 카드의 배경색을 설정

contentPadding : 자식 뷰 사이의 내부 간격을 조정

cardElevation : 그림자가 있는 카드를 생성


<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cardview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="1dp"
    app:cardCornerRadius="1dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="12dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">

        <ImageView
            android:id="@+id/list_cell_image"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            android:src="@drawable/expose_btn_n" />

        <LinearLayout
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginLeft="20dp"
            android:layout_weight="4"
            android:orientation="vertical">

            <TextView
                android:id="@+id/list_cell_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="타이틀"
                android:textSize="20dp"
                android:textStyle="bold" />

        </LinearLayout>
    </LinearLayout>
</android.support.v7.widget.CardView>



API 28 이상에서는

<android.support.v7.widget.CardView 대신에 <androidx.cardview.widget.CardView 로 변경해야 한다.


앱 build.gradle 에서도

implementation 'androidx.cardview:cardview:1.0.0' 를 사용해야 한다.

블로그 이미지

Link2Me

,
728x90

RecyclerView는 롤리팝 버전에서 새로이 등장한 위젯으로 안드로이드 ListView의 장/단점을 보완했다.

RecyclerView

ListView

 수직 스크롤, 수평 스크롤 지원

 수직 스크롤 지원

 ViewHolder 패턴 강제 적용

 (재활용 View 를 관리해주는 Class)

 ViewHolder 패턴 선택 적용

 Animation 적용이 쉽다
 (ItemAnimation : Item 항목의 Animation 관리 Class)

 Animation 적용이 어렵다


Target API에 따라서 숫자가 변경되니 Google SupportLibrary 사이트에서 확인해야 한다.
지원 라이브러리에 대한 내용은 https://developer.android.com/topic/libraries/support-library/features.html#v7 를 참조하면 도움된다.


Audio Player 에서 음악파일을 ListView 에 보여주는 걸 RecyclerView 로 변경하는 걸 해보고 신경써서 살펴볼 것만  적어둔다.

- 체크박스 선택 기능 및 전체 선택/해제 기능

- 버튼을 길게 누르면 체크박스를 선택할 수 있는 기능 (다중 선택)


아래 기술된 사항은 체크박스가 들어간 RecylcerView 라고 보면 된다.

보통 ListView 예제를 찾아보면 체크박스 선택하는 기능이 추가된 것은 찾기가 어렵다. 체크박스 선택 기능이 들어가면 코드 구현시 주의해야 할 사항이 있다.

아래 코드에는 ListView, RecyclerView 모두 체크박스 처리 부분이 들어가 있는데, RecyclerView 를 완벽하게 활용하지 못하므로 코드가 매끄럽지 못할 수 있다.


가장 먼저 앱 build.gradle 을 다음과 같이 추가해줬다. 이건 어디까지나 참고사항이므로 꼭 이대로 할 필요는 없다.

compileSdkVersion 23 으로 변경해서 테스트를 했다. 버전이 너무 높다고 좋은게 아닌거 같아서다.


apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "25.0.0"

    defaultConfig {
        applicationId "com.tistory.link2me.recyclerview"
        minSdkVersion 19
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.squareup.okhttp3:okhttp:3.9.0' // 서버와의 통신 라이브러리
    compile 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    compile 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    compile "com.android.support:cardview-v7:24.2.0"
    compile "com.android.support:design:24.2.0" // snack bar
}


dependencies 부분은 필요한 부분만 추가하면 된다.

RecyclerView 부분은 compile 'com.android.support:recyclerview-v7:24.2.0' 만 추가하면 된다.



activity_main.xml 파일에서 변경된 부분

ListView 부분이 RecyclerView 위젯으로 변경하면 된다. (색깔로 표시한 부분)

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

    <RelativeLayout
        android:id="@+id/list_view_relative2"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/whitegray" >

        <CheckBox
            android:id="@+id/lv_checkbox_all"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="전체선택"
            android:textColor="#000000"
            android:textStyle="bold" />

        <Button
            android:id="@+id/btn_cancle"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_marginLeft="10dp"
            android:layout_centerVertical="true"
            android:background="@drawable/btn_send_cancel" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="30dp"
            android:layout_toRightOf="@+id/btn_cancle"
            android:background="@drawable/btn_send_act" />

    </RelativeLayout>

    <ListView
        android:id="@+id/my_listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

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

    <RelativeLayout
        android:id="@+id/list_view_relative2"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/whitegray" >

        <CheckBox
            android:id="@+id/lv_checkbox_all"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="전체선택"
            android:textColor="#000000"
            android:textStyle="bold" />

        <Button
            android:id="@+id/btn_cancle"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_marginLeft="10dp"
            android:layout_centerVertical="true"
            android:background="@drawable/btn_send_cancel" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="30dp"
            android:layout_toRightOf="@+id/btn_cancle"
            android:background="@drawable/btn_send_act" />

    </RelativeLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>

</LinearLayout>


MainActivity.java 파일

변수 선언 부분

   private ListView audiolistView; // 리스트뷰
    private ArrayList<Song_Item> songsList = null; // 음악 전체 데이터 리스트
    private ArrayList<Song_Item> playList = new ArrayList<>(); // 선택한 음악 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter

    RecyclerView audiolistView; // 리스트뷰 (ListView 대신 RecyclerView)
    private ArrayList<Song_Item> songsList = new ArrayList<>(); // 음악 전체 데이터 리스트
    private ArrayList<Song_Item> playList = new ArrayList<>(); // 선택한 음악 데이터 리스트
    RecyclerView.Adapter listViewAdapter; // ListViewAdapter 대신 RecyclerView.Adapter
    RecyclerView.LayoutManager layoutManager;


protected void onCreate(Bundle savedInstanceState) 부분

       // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        songsList = new ArrayList<Song_Item>(); // ArrayList 생성
        audiolistView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
        audiolistView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

         // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        audiolistView = (RecyclerView) findViewById(R.id.recyclerview);
        audiolistView.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        audiolistView.setLayoutManager(layoutManager);

        listViewAdapter = new ListViewAdapter(songsList,this); // Adapter 생성
        audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅


체크박스 전체 선택/취소 버튼 부분

ListView 에서는 ListViewAdapter 부분에 전체 선택/해제 할 수 있는 기능을 넣어도 문제가 없었는데,

에러가 발생해서 public void setAllChecked(final boolean ischeked) 메소드 위치를 변경해서 해결했다.

        // all checkbox
        checkAll = (CheckBox) findViewById(R.id.lv_checkbox_all);
        checkAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (checkAll.isChecked() == true) {
                    isAll = true;
                    listViewAdapter.setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                } else {
                    isAll = false;
                    listViewAdapter.setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                }
            }
        });

        final Button calcel = (Button) findViewById(R.id.btn_cancle);
        calcel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = false;
                relative2.setVisibility(View.GONE);
                listViewAdapter.setAllChecked(false);
                checkAll.setChecked(false); // 전체 선택 체크박스 해제
                listViewAdapter.notifyDataSetChanged();
            }
        });

         // all checkbox
        checkAll = (CheckBox) findViewById(R.id.lv_checkbox_all);
        checkAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (checkAll.isChecked() == true) {
                    isAll = true;
                    setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                } else {
                    isAll = false;
                    setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                }
            }
        });

        final Button calcel = (Button) findViewById(R.id.btn_cancle);
        calcel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = false;
                relative2.setVisibility(View.GONE);
                setAllChecked(false);
                checkAll.setChecked(false); // 전체 선택 체크박스 해제
                listViewAdapter.notifyDataSetChanged();
            }
        });


음악데이터 추가 메소드도 위치를 변경해서 해결했다.


ListView 기반 ViewHolder 와 ListViewAdapter 부분

    class ViewHolder {
        public ImageView mImgAlbumArt;
        public TextView mTitle;
        public TextView mSubTitle;
        public TextView mDuration;
        public CheckBox checkbox;
    }

    private class ListViewAdapter extends BaseAdapter {
        Context context;
        public final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");

        public ListViewAdapter(Context context) {
            this.context = context;
        }

        // 음악 데이터 추가를 위한 메소드
        public void addItem(long mId, long AlbumId, String Title, String Artist, String Album, Integer Duration, String DataPath, boolean checkItem_flag) {
            Song_Item item = new Song_Item();
            item.setmId(mId);
            item.setAlbumId(AlbumId);
            item.setTitle(Title);
            item.setArtist(Artist);
            item.setAlbum(Album);
            item.setDuration(Duration);
            item.setDataPath(DataPath);
            item.setCheckBoxState(checkItem_flag);
            songsList.add(item);
        }

        // CheckBox를 모두 선택하는 메서드
        public void setAllChecked(final boolean ischeked) {
            final int tempSize = songsList.size();
            if (isAll == true) {
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(true);
                }
            } else {
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(false);
                }
            }
        }

        @Override
        public int getCount() {
            return songsList.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return songsList.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();
            final Integer index = Integer.valueOf(position);

            // 화면에 표시될 View
            if (convertView == null) {
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.song_item, parent, false);

                convertView.setBackgroundColor(0x00FFFFFF);
                convertView.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.mImgAlbumArt = (ImageView) convertView.findViewById(R.id.album_Image);
                viewHolder.mTitle = (TextView) convertView.findViewById(R.id.txt_title);
                viewHolder.mSubTitle = (TextView) convertView.findViewById(R.id.txt_subtitle);
                viewHolder.mDuration = (TextView) convertView.findViewById(R.id.txt_duration);
                viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.list_cell_checkbox);

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            // Song_Item 에서 position 에 위치한 데이터 참조 획득
            final Song_Item songItem = songsList.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            Uri albumArtUri = ContentUris.withAppendedId(artworkUri, songItem.getAlbumId());
            Picasso.with(convertView.getContext()).load(albumArtUri).error(R.drawable.ic_launcher).into(viewHolder.mImgAlbumArt);
            viewHolder.mTitle.setText(songItem.getTitle());
            viewHolder.mSubTitle.setText(songItem.getArtist());

            int dur = (int) songItem.getDuration();
            int hrs = (dur / 3600000);
            int mns = (dur / 60000) % 60000;
            int scs = dur % 60000 / 1000;
            String songTime = String.format("%02d:%02d:%02d", hrs, mns, scs);
            if (hrs == 0) {
                songTime = String.format("%02d:%02d", mns, scs);
            }
            viewHolder.mDuration.setText(songTime);

            if (isMSG == false) {
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        playList.clear();
                        PlayList(songsList.get(position).getmId(), songsList.get(position).getAlbumId(), songsList.get(position).getTitle(), songsList.get(position).getArtist(),
                                songsList.get(position).getAlbum(), songsList.get(position).getDuration(), songsList.get(position).getDataPath());

                        System.out.println("position=" + position + " 노래제목 : " + songsList.get(position).getTitle());

                        Intent intent = new Intent(MainActivity.this, Player.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | intent.FLAG_ACTIVITY_CLEAR_TOP);
                        intent.putExtra("playList", playList); // 배열 데이터
                        startActivity(intent);
                    }
                });

                convertView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        isMSG = true;
                        relative2.setVisibility(View.VISIBLE);
                        listViewAdapter.notifyDataSetChanged();
                        return false;
                    }
                });

            } else {
                if (isMSG == true) {
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.

                    viewHolder.checkbox.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (songsList.get(position).isCheckBoxState() == true) {
                                songsList.get(position).setCheckBoxState(false);
                            } else {
                                songsList.get(position).setCheckBoxState(true);
                            }
                        }
                    });

                    convertView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (viewHolder.checkbox.isChecked() == false) {
                                viewHolder.checkbox.setChecked(true);
                                songsList.get(position).setCheckBoxState(true);
                            } else {
                                viewHolder.checkbox.setChecked(false);
                                songsList.get(position).setCheckBoxState(false);
                            }
                        }
                    });

                }
            }

            // 재사용 문제 해결
            if (songsList.get(position).isCheckBoxState() == true) {
                viewHolder.checkbox.setChecked(true);
            } else {
                viewHolder.checkbox.setChecked(false);
            }

            return convertView;
        }

    }


RecyclerView 기반 ListViewAdapter 부분

    class ListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        Context mContext;
        private ArrayList<Song_Item> songsList;
        Song_Item songItem;
        int Position;

        public final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");

        public ListViewAdapter(ArrayList<Song_Item> items,Context context) {
            songsList = items;
            mContext = context;
        }

        public class ViewHolder extends RecyclerView.ViewHolder  {
            public ImageView mImgAlbumArt;
            public TextView mTitle;
            public TextView mSubTitle;
            public TextView mDuration;
            public CheckBox checkbox;

            public ViewHolder(final View itemView) {
                super(itemView);
                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                mImgAlbumArt = (ImageView) itemView.findViewById(R.id.album_Image);
                mTitle = (TextView) itemView.findViewById(R.id.txt_title);
                mSubTitle = (TextView) itemView.findViewById(R.id.txt_subtitle);
                mDuration = (TextView) itemView.findViewById(R.id.txt_duration);
                checkbox = (CheckBox) itemView.findViewById(R.id.list_cell_checkbox);

                itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        isMSG = true;
                        relative2.setVisibility(View.VISIBLE);
                        listViewAdapter.notifyDataSetChanged();
                        return false;
                    }
                });

                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Position = getAdapterPosition();
                        if (isMSG == false) {
                            playList.clear();
                            PlayList(songsList.get(Position).getmId(), songsList.get(Position).getAlbumId(), songsList.get(Position).getTitle(), songsList.get(Position).getArtist(),
                                    songsList.get(Position).getAlbum(), songsList.get(Position).getDuration(), songsList.get(Position).getDataPath());

                            System.out.println("position=" + Position + " 노래제목 : " + songsList.get(Position).getTitle());

                            Intent intent = new Intent(MainActivity.this, Player.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | intent.FLAG_ACTIVITY_CLEAR_TOP);
                            intent.putExtra("playList", playList); // 배열 데이터
                            mContext.startActivity(intent);
                        } else {
                            //Toast.makeText(mContext, ""+Position, Toast.LENGTH_SHORT).show();
                            if (checkbox.isChecked() == false) {
                                checkbox.setChecked(true);
                                songsList.get(Position).setCheckBoxState(true);
                            } else {
                                checkbox.setChecked(false);
                                songsList.get(Position).setCheckBoxState(false);
                            }
                        }

                    }
                });

            }

            public void setAudioItem(Song_Item item, final int position) {
                songItem = item;
                // 아이템 내 각 위젯에 데이터 반영
                Uri albumArtUri = ContentUris.withAppendedId(artworkUri, songItem.getAlbumId());
                Picasso.with(mContext).load(albumArtUri).error(R.drawable.ic_launcher).into(mImgAlbumArt);
                mTitle.setText(songItem.getTitle());
                mSubTitle.setText(songItem.getArtist());

                int dur = (int) songItem.getDuration();
                int hrs = (dur / 3600000);
                int mns = (dur / 60000) % 60000;
                int scs = dur % 60000 / 1000;
                String songTime = String.format("%02d:%02d:%02d", hrs, mns, scs);
                if (hrs == 0) {
                    songTime = String.format("%02d:%02d", mns, scs);
                }
                mDuration.setText(songTime);

                if (isMSG == false) {
                    checkbox.setVisibility(View.GONE);
                } else {
                    if (isMSG == true) {
                        checkbox.setVisibility(View.VISIBLE);
                        checkbox.setTag(position); // This line is important.
                        checkbox.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //Toast.makeText(mContext, ""+position, Toast.LENGTH_SHORT).show();
                                if (songsList.get(position).isCheckBoxState() == true) {
                                    songsList.get(position).setCheckBoxState(false);
                                } else {
                                    songsList.get(position).setCheckBoxState(true);
                                }
                            }
                        });
                    }
                }

                // 재사용 문제 해결
                if (songsList.get(position).isCheckBoxState() == true) {
                    checkbox.setChecked(true);
                } else {
                    checkbox.setChecked(false);
                }
            }

        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // 새로운 뷰를 만든다.
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_item,parent,false);
            ViewHolder viewHolder = new ViewHolder(itemView);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
            // ListView의 getView 부분을 담당하는 메소드
            // Song_Item 에서 position 에 위치한 데이터 참조 획득
            songItem = songsList.get(position);
            ((ViewHolder) holder).setAudioItem(songItem,position);
        }

        @Override
        public int getItemCount() {
            return songsList.size(); // 데이터 개수 리턴
        }

    }


이런 부분을 수정해서 RecyclerView 기반으로 변경하는데 성공했다.

좀 더 다른 예제도 활용해서 테스트를 해보고 RecyclerView 의 장점(?)을 파악해보련다.

블로그 이미지

Link2Me

,
728x90

안드로이드 Checkbox

- isChecked () 메소드를 통해 체크 박스가 선택되어 있는지를 확인할 수 있다.
- setChecked () 메소드를 통해 상태값을 지정할 수 있다.
- 체크된 상태가 변경될 경우에는 onCheckedChanged () 메소드가 자동으로 호출되어 후작업을 하게 된다.
  onCheckedChanged ()메소드에 실제 동작을 추가하기 위해서는
  setOnCheckedChangeListener () 메소드를 통해 OnCheckedChangeListener 인터페이스를 구현하여 추가하면 된다.

checkBox1 = (CheckBox) findViewById(R.id.checkBox1);
if(checkBox1.isChecked()){
    textView1.setText("체크박스 선택됨");
} else {
    textView1.setText("체크박스 선택 취소됨");
}


ListView Checkbox 에 대한 자료를 검색하면 http://www.mysamplecode.com/2012/07/android-listview-checkbox-example.html 를 많이들 추천하고 있다.

테스트 해 본 결과 만족스러운 결과를 얻을 수 없었다.


ListView 에 Checkbox를 포함시켜서 스크롤 시키면 체크박스 상태가 변한다.

Adapter 는 자체적으로 메모리를 유연하게 사용하기 위해서 자동으로 화면 바깥쪽으로 벗어난 Adapter 객체를 반대쪽으로 이동시켜 재사용한다.


체크박스 객체 자체를 ArrayList, Hashmap 등에 담아서 사용해 봤는데 해결이 안된다.

많은 자료들을 검색하고 테스트 해보는 중인데 어렵다. 3주동안 해결을 못했다는 내용도 있더라.


스크롤뷰 문제만 빼면 나머지 부분은 성공(?)된 기능이라고 볼 수 있다.

- 전체 선택 및 해제 : 잘 됨

- 부분 선택 및 해제 : 스크롤이 안된 상태에서는 정상, 스크롤 하면 데이터가 꼬여버림.


서버에서 가져온 데이터를 ArrayList 에 저장하고 저장된 데이터 중에서 체크된 것만 뽑아내는 로직이다.

이 로직은 검증용으로 활용하기 위한 용도다.

StringBuffer responseText = new StringBuffer();
responseText.append("The following were checkBoxState...");
for(int i=0;i<arrayList.size();i++){
    if(arrayList.get(i).isCheckBoxState() == true){
        responseText.append("\n" + arrayList.get(i).getUid());
    }
}
Toast.makeText(getApplicationContext(),responseText, Toast.LENGTH_LONG).show();
 



해결방안 : 아래 소스를 http://blog.naver.com/akdlqnsl/220469620347 참조/수정하면 해결 가능하다.

이 소스를 통해서 체크박스 선택에 문제가 없다는 걸 확인했다.

다른 게시글에 나온 방법들로는 해결을 못했다. 내가 구현한 ListViewAdatapter 생성자 구현방법이 위 예제에 나온 것들과 달라서 에러가 발생하더라.

체크박스 처리하는 부분을 구현해보면서 ListViewAdapter 에 대한 이해를 좀 더 할 수 있게 되었다.

ArrayList, Hashmap 에 대한 것을 더 많이 테스트해보고 완벽하게 이해하는 것이 다양한 응용을 가능하게 할 수 있을 거 같다.


public class Address_Item {
    // PersonData 정보를 담고 있는 객체 생성
    private String profile_image; // 이미지 경로를 String으로 받기 위해서
    private String uid;
    private String name;
    private String mobileNO;
    private String officeNO;
    boolean checkBoxState;

    public Address_Item() {
    }

    public Address_Item(String profile_image, String uid, String name, String mobileNO, String officeNO, boolean checkBoxState) {
        this.profile_image = profile_image;
        this.uid = uid;
        this.name = name;
        this.mobileNO = mobileNO;
        this.officeNO = officeNO;
        this.checkBoxState = checkBoxState;
    }

    public String getProfile_image() {
        return profile_image;
    }

    public void setProfile_image(String profile_image) {
        this.profile_image = profile_image;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobileNO() {
        return mobileNO;
    }

    public void setMobileNO(String mobileNO) {
        this.mobileNO = mobileNO;
    }

    public String getOfficeNO() {
        return officeNO;
    }

    public void setOfficeNO(String officeNO) {
        this.officeNO = officeNO;
    }

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

    public void setCheckBoxState(boolean checkBoxState) {
        this.checkBoxState = checkBoxState;
    }
}


=== MainActivity.java ====


import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.telephony.PhoneNumberUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "AppPermission";
    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;
    private Button btn_chatting;
    private Button btn_msg;

    private ArrayList<Address_Item> arrayList = null; // 데이터 리스트
    static ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter
    ProgressDialog mProgressDialog;

    static boolean isMSG = false;
    static boolean isAll = false;
    static boolean flag = false;
    static CheckBox check_all;

    RelativeLayout relative1;
    RelativeLayout relative2;

    Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this.getBaseContext();

        isMSG = false;
        isAll = false;

        editText = (EditText) findViewById(R.id.et_text01);

        // Relative layout 정의
        relative1 = (RelativeLayout) findViewById(R.id.list_view_relative1);
        relative2 = (RelativeLayout) findViewById(R.id.list_view_relative2);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        arrayList = new ArrayList<Address_Item>(); // ArrayList 생성

        listView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","" )
                String postParams = builder.build().getEncodedQuery();
                new getJSONData().execute(Value.IPADDRESS + "/get_json.php", postParams);
                hideSoftKeyboard();
            }
        });

        if (isMSG == false) {
            relative1.setVisibility(View.VISIBLE);
            relative2.setVisibility(View.GONE);
        } else if (isMSG == true) {
            relative1.setVisibility(View.GONE);
            relative2.setVisibility(View.VISIBLE);
        }

        btn_msg = (Button) findViewById(R.id.lv_btn_msg);
        btn_msg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = true;
                relative1.setVisibility(View.GONE);
                relative2.setVisibility(View.VISIBLE);
                listViewAdapter.notifyDataSetChanged();
            }
        });

        // all checkbox
        check_all = (CheckBox) findViewById(R.id.lv_checkbox_all);
        check_all.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (check_all.isChecked() == false) {
                    isAll = false;
                    listViewAdapter.setAllChecked(check_all.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                } else if (check_all.isChecked() == true) {
                    isAll = true;
                    listViewAdapter.setAllChecked(check_all.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                }
            }
        });

        Button calcel = (Button) findViewById(R.id.btn_cancle);
        calcel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = false;
                relative1.setVisibility(View.VISIBLE);
                relative2.setVisibility(View.GONE);
                listViewAdapter.setAllChecked(false);
                check_all.setChecked(false); // 전체 선택 체크박스 해제
                listViewAdapter.notifyDataSetChanged();
            }
        });

        Button send = (Button) findViewById(R.id.btn_send);
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StringBuffer responseText = new StringBuffer();
                responseText.append("The following were selected...");
                for(int i=0;i<arrayList.size();i++){
                    if(arrayList.get(i).isCheckBoxState() == true){
                        responseText.append("\n" + arrayList.get(i).getUid());
                    }
                }
                Toast.makeText(getApplicationContext(),responseText, Toast.LENGTH_LONG).show();
            }
        });

    }

    private void hideSoftKeyboard() { // 소프트 키보드 숨기기
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(editText.getApplicationWindowToken(), 0);
    }

    class getJSONData extends AsyncTask<String, Void, String> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // Create a progressdialog
            mProgressDialog = new ProgressDialog(MainActivity.this);
            mProgressDialog.setTitle("Personal Profile JSON Parse");
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setIndeterminate(false);
            mProgressDialog.show();  // Show progressdialog
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0], params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result) {
            searchJSON = result;
            showList();
            mProgressDialog.dismiss();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS = "result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "name";
    private static final String TAG_MobileNO = "mobileNO";
    private static final String TAG_OfficeNO = "officeNO";
    private static final String TAG_Image = "photo"; // 이미지 필드
    JSONArray peoples = null;

    protected void showList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            arrayList.clear(); // 서버에서 가져온 데이터 초기화
            for (int i = 0; i < peoples.length(); i++) {
                JSONObject c = peoples.getJSONObject(i);
                final String uid = c.getString(TAG_UID);
                final String name = c.getString(TAG_NAME);
                final String mobileNO = c.getString(TAG_MobileNO);
                final String officeNO = c.getString(TAG_OfficeNO);
                final String Profile_Image = c.getString(TAG_Image);

                // 서버에서 가져온 데이터 저장
                listViewAdapter.addItem(Profile_Image, uid, name, mobileNO, officeNO, false);
            }

            runOnUiThread(new Runnable() { // 화면에 반영하기 위하여 runOnUiThread()를 호출하여 실시간 갱신한다.
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    class ViewHolder {
        public LinearLayout child_layout;
        public ImageView profile_Image;
        public TextView tv_name;
        public TextView tv_mobileNO;
        public TextView tv_officeNO;
        public ImageView child_btn;
        public CheckBox checkbox;
    }

    private class ListViewAdapter extends BaseAdapter {
        ImageLoader imageLoader;
        Context mContext;

        public ListViewAdapter(Context context) {
            this.mContext = context;
            imageLoader = new ImageLoader(context);
        }

        // CheckBox를 모두 선택하는 메서드
        public void setAllChecked(final boolean ischeked) {
            final int tempSize = arrayList.size();
            if(ischeked == true){
                for (int ia = 0; ia < tempSize; ia++) {
                    arrayList.get(ia).setCheckBoxState(true);
                    notifyDataSetChanged();
                }
            } else {
                for (int a = 0; a < tempSize; a++) {
                    arrayList.get(a).setCheckBoxState(false);
                    notifyDataSetChanged();
                }
            }
        }

        // 아이템 데이터 추가를 위한 메소드
        public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO, boolean checkItem_flag) {
            Address_Item item = new Address_Item();
            item.setProfile_image(profile_image);
            item.setUid(uid);
            item.setName(name);
            item.setMobileNO(mobileNO);
            item.setOfficeNO(officeNO);
            item.setCheckBoxState(checkItem_flag);

            arrayList.add(item);
        }
    }


        @Override
        public int getCount() {
            return arrayList.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return arrayList.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();
            final int checkBoxPosition = position;
            final Integer index = Integer.valueOf(position);

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            final Address_Item addressItem = arrayList.get(position);

            // 화면에 표시될 View
            if (convertView == null) {
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.address_item, parent, false);

                convertView.setBackgroundColor(0x00FFFFFF);
                convertView.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
                viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
                viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
                viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
                viewHolder.child_btn = (ImageView) convertView.findViewById(R.id.child_Btn);
                viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.list_cell_checkbox);
                if(viewHolder.checkbox != null){
                    // 체크박스의 상태 변화를 체크한다.
                    viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            int getPosition = (Integer) buttonView.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                            arrayList.get(index).setSelected(buttonView.isChecked());
                        }
                    });
                }

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            // 아이템 내 각 위젯에 데이터 반영
            if (addressItem.getProfile_image().equals("")) {
                final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
                viewHolder.profile_Image.setImageBitmap(Base_Profile);
            } else {
                String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
                imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
            }

            viewHolder.tv_name.setText(addressItem.getName());
            viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()));
            viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()));

            if (isMSG == false) {
                viewHolder.child_btn.setVisibility(View.VISIBLE);
                viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                       
                    }
                });
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ===" + addressItem.getUid(), Toast.LENGTH_SHORT).show();
                    }
                });
            } else {
                if (isMSG == true) {
                    convertView.setOnClickListener(null); // 체크박스가 활성화되면 convertView 클릭 리스너 비활성화
                    viewHolder.child_btn.setVisibility(View.GONE);
                    //convertView.setClickable(false);
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.
                    //viewHolder.checkbox.setChecked(false);

                }
            }

            // 체크된 아이템인지 판단할 boolean 변수
            for(int i=0; i < arrayList.size();i++){
                if(arrayList.get(i).isCheckBoxState() == true){
                    viewHolder.checkbox.
setCheckBoxState(true);
                } else {
                    viewHolder.checkbox.
setCheckBoxState(false);
                }
                notifyDataSetChanged();
            }

            return convertView;
        }

}

블로그 이미지

Link2Me

,
728x90

Android Studio 에서 연락처 저장하는 기능으로 구현에 필요한 내용은 모두 적어둔다.

서버에서 읽어온 사진 이미지까지 저장할 수 있도록 했다.

서버에서 사진 이미지를 읽어오고 나면 Cache에 저장했으므로

MemoryCache memoryCache = new MemoryCache();
Bitmap cachedImage = memoryCache.get(strPhoto);

를 했는데도 불구하고 null 값이 나온다.


연락처(Contacts)에 전화번호를 저장하는 기능은 검사하여 있으면 삭제후 다시 신규 저장하는 방식으로 구현되어 있는데, 이런 로직보다는 데이터가 있으면 비교하여 달라진 것이 있으면 Update 하고 동일하면 패스하는 로직으로 구현하는 것이 바람직하다. 이런 구현방식은 개발자의 몫이기 때문에 여기에는 적어놓지 않는다.


import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.util.Log;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class Contacts {
    public Contacts(){
        
    }
    
     // 전화번호부에 존재하는 데이터인지 여부 검사
    // 표시 이름과 휴대폰번호를 기준으로 ContactId 구하기
    public long
ContactsIDExistCheck(ContentResolver cr, String display_name, String number) {
        long rawContactId = -1;

        Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
        String[] projection = { PhoneLookup._ID, PhoneLookup.TYPE, PhoneLookup.DISPLAY_NAME };
        Cursor cursor = null;
        try {
            cursor = cr.query(contactUri, projection, null, null, null);
            if (cursor.moveToFirst()) {
                do {
                    String PhoneName = cursor.getString(cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME));
                    if (display_name.equals(PhoneName)) {
                        rawContactId = cursor.getLong(cursor.getColumnIndex(PhoneLookup._ID));
                        System.out.println(" ContactId = " + rawContactId + " 성명 : " + PhoneName);
                    }
                } while (cursor.moveToNext());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return rawContactId;
    }
   

    public void ContactsIDdelete(ContentResolver cr, Context context, Integer contactId){
        // 전화번호부 삭제
        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
           
        //remove contact from raw_contact table
        ops.add(ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI).
        withSelection(ContactsContract.RawContacts.CONTACT_ID + "=?", new String[]{String.valueOf(contactId)}).
        build());              
               
        try {
            cr.applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void ContactsIDinsert(ContentResolver cr, Context context, String strName, String strMobileNO) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                .build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, strName)
                .build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
                .withValue(Phone.NUMBER, strMobileNO)
                .withValue(Phone.TYPE, Phone.TYPE_MOBILE)
                .build());

        try {
            cr.applyBatch(ContactsContract.AUTHORITY, ops);
            Toast.makeText(context, "연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }
           
    }
    
    public void ContactsIDinsert(ContentResolver cr, Context context, String strName, String strMobileNO, String strOfficeNO, String strEmail, String strPhoto) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                .build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, strName)
                .build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
                .withValue(Phone.NUMBER, strMobileNO)
                .withValue(Phone.TYPE, Phone.TYPE_MOBILE)
                .build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
                .withValue(Phone.NUMBER, strOfficeNO)
                .withValue(Phone.TYPE, Phone.TYPE_WORK)
                .build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
                .withValue(Email.DATA, strEmail)
                .withValue(Email.TYPE, Email.TYPE_WORK)
                .build());

        MemoryCache memoryCache = new MemoryCache();
        Bitmap cachedImage = memoryCache.get(strPhoto);
        System.out.println("cachedImage ===" + cachedImage);
        if (cachedImage != null) {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            cachedImage.compress(Bitmap.CompressFormat.JPEG, 60, bos);

            byte[] bytes = bos.toByteArray();

            ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
                    .withValue(ContactsContract.CommonDataKinds.Photo.DATA15, bytes)
                    .build());
            try {
                bos.flush();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            cr.applyBatch(ContactsContract.AUTHORITY, ops);
            Toast.makeText(context, "연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }          
    }   
}


메모리 캐쉬 파일을 수정했다.

import android.graphics.Bitmap;
import android.util.Log;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class MemoryCache {
    private static final String TAG = "MemoryCache";
    public static  Map<String, Bitmap> cache= Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering
    private long size=0;//current allocated size
    private long limit=1000000;//max memory in bytes

    public MemoryCache(){
        //use 25% of available heap size
        setLimit(Runtime.getRuntime().maxMemory()/4);
    }

    public void setLimit(long new_limit){
        limit=new_limit;
        Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
    }

    public static Bitmap get(String id){
        try{
            if(!cache.containsKey(id))
                return null;
            return cache.get(id);
        }catch(NullPointerException ex){
            ex.printStackTrace();
            return null;
        }
    }

    public void put(String id, Bitmap bitmap){
        try{
            if(cache.containsKey(id))
                size-=getSizeInBytes(cache.get(id));
            cache.put(id, bitmap);
            size+=getSizeInBytes(bitmap);
            checkSize();
        }catch(Throwable th){
            th.printStackTrace();
        }
    }

    private void checkSize() {
        Log.i(TAG, "cache size="+size+" length="+cache.size());
        if(size>limit){
            Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated
            while(iter.hasNext()){
                Entry<String, Bitmap> entry=iter.next();
                size-=getSizeInBytes(entry.getValue());
                iter.remove();
                if(size<=limit)
                    break;
            }
            Log.i(TAG, "Clean cache. New size "+cache.size());
        }
    }

    public void clear() {
        try{
            //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78
            cache.clear();
            size=0;
        }catch(NullPointerException ex){
            ex.printStackTrace();
        }
    }

    long getSizeInBytes(Bitmap bitmap) {
        if(bitmap==null)
            return 0;
        return bitmap.getRowBytes() * bitmap.getHeight();
    }
}


MainActivity.java 파일 내에 ListViewAdapter 부분

 private class ListViewAdapter extends BaseAdapter {
    ImageLoader imageLoader;
    Context context;

    public ListViewAdapter(Context context) {
        this.context = context;
        imageLoader = new ImageLoader(context);
    }

    @Override
    public int getCount() {
        return arrayList.size(); // 데이터 개수 리턴
    }

    @Override
    public Object getItem(int position) {
        return arrayList.get(position);
    }

    // 지정한 위치(position)에 있는 데이터 리턴
    @Override
    public long getItemId(int position) {
        return position;
    }

    // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;
        final Context context = parent.getContext();

        // 화면에 표시될 View
        if(convertView == null){
            viewHolder = new ViewHolder();

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.address_item,parent,false);

            convertView.setBackgroundColor(0x00FFFFFF);
            convertView.invalidate();

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
            viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
            viewHolder.child_btn = (ImageView) convertView.findViewById(R.id.child_Btn);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        final Address_Item addressItem = arrayList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        // 선택된 row의 데이터를 표시한다. 표시될 view는 address_itemm.xml 의 각 항목을 이용하여 표시한다.
        //System.out.println("imageurl==" + addressItem.getProfile_image());
        if(addressItem.getProfile_image().equals("")){
            final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
            viewHolder.profile_Image.setImageBitmap(Base_Profile);
        } else {
            String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
            imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
        }

        viewHolder.tv_name.setText(addressItem.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()));

        final String[] items ={"휴대폰 전화걸기","사무실전화 걸기", "연락처 저장"};
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("해당작업을 선택하세요");
        builder.setItems(items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show();
                switch (which){
                    case 0:
                        if(addressItem.getMobileNO().length() ==0){
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog1 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getMobileNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                   // TODO: Consider calling
                                                   if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                                                       requestPermissions(new String[]{Manifest.permission.CALL_PHONE},1000);
                                                   }

                                                   return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog1.show();
                        break;
                    case 1:
                        if(addressItem.getOfficeNO().length() ==0){
                            Toast.makeText(context, "전화걸 사무실 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog2 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getOfficeNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                    // TODO: Consider calling
                                                    return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog2.show();
                        break;
                    case 2:
                        // 연락처 저장 함수 호출
                        AlertDialog.Builder SaveContact = new AlertDialog.Builder(context);
                        SaveContact.setMessage("전화번호를 저장하시겠습니까?");
                        DialogInterface.OnClickListener save = new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                int rawContactId = 0;
                                Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                                ContentResolver cr = getContentResolver();
                                String strContactName = addressItem.getName();
                                String strMobileNO = addressItem.getMobileNO();
                                String strofficeNO =addressItem.getOfficeNO();
                                String strEmail ="";
                                String strPhoto ="";
                                if(addressItem.getProfile_image().length()>0){
                                    strPhoto =Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
                                }
                                //System.out.println("strPhoto ==="+ strPhoto);

                                rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName,strMobileNO);
                                if(rawContactId > 0){
                                    // 기존 전화번호가 존재하면 삭제하고 새로 입력
                                    phonebook.ContactsIDdelete(cr, context, rawContactId);
                                }
                                phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                            }
                        };

                        DialogInterface.OnClickListener cancel = new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        };
                        SaveContact.setPositiveButton("저장", save);
                        SaveContact.setNegativeButton("취소", cancel);
                        SaveContact.show();
                        break;
                }
            }
        });
        builder.create();

        viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                builder.show();
            }
        });

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ==="+ addressItem.getUid(), Toast.LENGTH_SHORT).show();
            }
        });

        return convertView;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
        Address_Item item = new Address_Item();
        item.setProfile_image(profile_image);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        arrayList.add(item);
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1000) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this, "권한 요청을 승인했습니다.", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "권한 요청을 거부했습니다.", Toast.LENGTH_SHORT).show();
            }
        }
    }

}


블로그 이미지

Link2Me

,
728x90

Custom ListViewAdapter 에서 세부적으로 처리할 여러가지 기능 등을 테스트하고 있다.

전화걸기 기능 추가했는데 eclipse 버전 낮은 것에서 동작하던 것이 Android Studio 버전이 높은 걸 선택하면서 Permission 체크하는 기능을 추가해야만 동작하는 거 같다.


import android.Manifest;

private class ListViewAdapter extends BaseAdapter {
    ImageLoader imageLoader;
    Context context;

    public ListViewAdapter(Context context) {
        this.context = context;
        imageLoader = new ImageLoader(context);
    }

    @Override
    public int getCount() {
        return arrayList.size(); // 데이터 개수 리턴
    }

    @Override
    public Object getItem(int position) {
        return arrayList.get(position);
    }

    // 지정한 위치(position)에 있는 데이터 리턴
    @Override
    public long getItemId(int position) {
        return position;
    }

    // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;
        final Context context = parent.getContext();

        // 화면에 표시될 View
        if (convertView == null) {
            viewHolder = new ViewHolder();

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.person_item, parent, false);

            convertView.setBackgroundColor(0x00FFFFFF);
            convertView.invalidate();

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
            viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
            viewHolder.chid_btn = (Button) convertView.findViewById(R.id.child_Btn);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        final PesonData pesonData = arrayList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        // 선택된 row의 데이터를 표시한다. 표시될 view는 person_item.xml 의 각 항목을 이용하여 표시한다.
        System.out.println("imageurl==" + pesonData.getProfile_image());
        if (pesonData.getProfile_image().equals("")) {
            final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
            viewHolder.profile_Image.setImageBitmap(Base_Profile);
        } else {
            final String photoURL = Value.IPADDRESS + "/photos/" + pesonData.getProfile_image();
            imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
        }

        viewHolder.tv_name.setText(pesonData.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

        viewHolder.chid_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 버튼이 눌렸을 때 다음 액션 처리를 여기에 코드를 적어서 처리한다.
                Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                vibe.vibrate(50);

                AlertDialog showdialog = new AlertDialog.Builder(MainActivity.this)
                        .setTitle(pesonData.getName())
                        .setMessage(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()) + " 통화하시겠습니까?")
                        .setPositiveButton("예",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog, int which) {

                                        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(pesonData.getMobileNO())));
                                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                        if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                            // TODO: Consider calling
                                            //    ActivityCompat#requestPermissions
                                            // here to request the missing permissions, and then overriding
                                            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                            //                                          int[] grantResults)
                                            // to handle the case where the user grants the permission. See the documentation
                                            // for ActivityCompat#requestPermissions for more details.
                                            return;
                                        }
                                        startActivity(intent);
                                    }
                                })
                        .setNegativeButton(
                                "아니오",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog,int which) {
                                        dialog.dismiss();
                                    }
                                }).create();
                showdialog.show();
            }
        });

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ==="+ pesonData.getUid(), Toast.LENGTH_SHORT).show();
            }
        });

        return convertView;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
        PesonData item = new PesonData();
        item.setProfile_image(profile_image);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        arrayList.add(item);
    }
}


2017.4.30 수정 보완

휴대폰 전화걸기, 사무실전화걸기, 연락처 저장 선택메뉴 추가

연락처 저장시 사진 이미지 저장은 아직 해결 못한 상태


class ViewHolder {
    public LinearLayout child_layout;
    public ImageView profile_Image;
    public TextView tv_name;
    public TextView tv_mobileNO;
    public TextView tv_officeNO;
    public ImageView child_btn;
}

private class ListViewAdapter extends BaseAdapter {
    ImageLoader imageLoader;
    Context context;

    public ListViewAdapter(Context context) {
        this.context = context;
        imageLoader = new ImageLoader(context);
    }

    @Override
    public int getCount() {
        return arrayList.size(); // 데이터 개수 리턴
    }

    @Override
    public Object getItem(int position) {
        return arrayList.get(position);
    }

    // 지정한 위치(position)에 있는 데이터 리턴
    @Override
    public long getItemId(int position) {
        return position;
    }

    // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;
        final Context context = parent.getContext();

        // 화면에 표시될 View
        if(convertView == null){
            viewHolder = new ViewHolder();

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.address_item,parent,false);

            convertView.setBackgroundColor(0x00FFFFFF);
            convertView.invalidate();

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
            viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
            viewHolder.child_btn = (ImageView) convertView.findViewById(R.id.child_Btn);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        final Address_Item addressItem = arrayList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        // 선택된 row의 데이터를 표시한다. 표시될 view는 address_itemm.xml 의 각 항목 이용 표시한다.
        System.out.println("imageurl==" + addressItem.getProfile_image());
        if(addressItem.getProfile_image().equals("")){
            final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
            viewHolder.profile_Image.setImageBitmap(Base_Profile);
        } else {
            final String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
            imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
        }

        viewHolder.tv_name.setText(addressItem.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()));

        final String[] items ={"휴대폰 전화걸기","사무실전화 걸기", "연락처 저장"};
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("해당작업을 선택하세요");
        builder.setItems(items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show();
                switch (which){
                    case 0:
                        if(addressItem.getMobileNO().length() ==0){
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog1 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getMobileNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                    // TODO: Consider calling
                                                    return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog1.show();
                        break;
                    case 1:
                        if(addressItem.getOfficeNO().length() ==0){
                            Toast.makeText(context, "전화걸 사무실 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog2 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getOfficeNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                    // TODO: Consider calling
                                                    return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog2.show();
                        break;
                    case 2:
                        // 연락처 저장 함수 호출
                        AlertDialog.Builder SaveContact = new AlertDialog.Builder(context);
                        SaveContact.setMessage("전화번호를 저장하시겠습니까?");
                        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                int rawContactId = 0;
                                Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                                ContentResolver cr = getContentResolver();
                                String strContactName = addressItem.getName();
                                String strMobileNO = addressItem.getMobileNO();
                                String strofficeNO =addressItem.getOfficeNO();
                                String strEmail ="";
                                String strPhoto ="";

                                rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName);
                                if(rawContactId > 0){
                                    // 기존 전화번호가 존재하면 삭제하고 새로 입력
                                    System.out.println("ContactID is exist !! Need delete!!");
                                    phonebook.ContactsIDdelete(cr, context, rawContactId);
                                }
                                phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                            }
                        };

                        DialogInterface.OnClickListener cancel = new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        };
                        SaveContact.setPositiveButton("저장", listener);
                        SaveContact.setNegativeButton("취소", cancel);
                        SaveContact.show();
                        break;
                }
            }
        });
        builder.create();

        viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 버튼이 눌렸을 때 다음 액션 처리를 여기에 코드를 적어서 처리한다.
                Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                vibe.vibrate(50);

                builder.show();
            }
        });

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ==="+ addressItem.getUid(), Toast.LENGTH_SHORT).show();
            }
        });

        return convertView;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
        Address_Item item = new Address_Item();
        item.setProfile_image(profile_image);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        arrayList.add(item);
    }
}


연락처 저장 소스는 http://stackoverflow.com/questions/4744187/how-to-add-new-contacts-in-android 참조하면 나온다.

블로그 이미지

Link2Me

,
728x90

이미지 다운로드하는 파일을 구해서 테스트 해보니 사진 이미지 크기가 일정하지 않는 문제점이 있다.

서버에서 읽어들이는 파일 사이즈 크기를 알아보기 위해서

System.out.println("Original Photo Image width ==="+ width_tmp);

System.out.println("Original Photo Image height ==="+ height_tmp);

를 추가해서 size 를 확인한다.


 private Bitmap decodeFile(File f){
    try {
        //decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f),null,options);

        //Find the correct scale value. It should be the power of 2.
        final int REQUIRED_SIZE= 70;
        int width_tmp=options.outWidth;
        int height_tmp=options.outHeight;
        System.out.println("Original Photo Image width ==="+ width_tmp);
        System.out.println("Original Photo Image height ==="+ height_tmp);
        int scale=1;
        while(true){
            if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                break;
            width_tmp/=2;
            height_tmp/=2;
            scale*=2;
        }

        //decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize=scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}


수정한 코드

private Bitmap decodeFile(File f){
    try {
            //decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f),null,options);

            //Find the correct scale value. It should be the power of 2.
            // final int REQUIRED_SIZE= 70;
            int width_tmp=options.outWidth;
            int height_tmp=options.outHeight;
            System.out.println("Original Photo Image width ==="+ width_tmp);
            System.out.println("Original Photo Image height ==="+ height_tmp);

            /***********수정 코드 *********************************************/

            // 높이 160 은 원하는 크기로 수정 사용하면 됨
            int width = (int)(width_tmp * 160.0 / height_tmp);
            Bitmap orgImage = BitmapFactory.decodeStream(new FileInputStream(f));
            return  Bitmap.createScaledBitmap(orgImage, width, 160,true);
    } catch (FileNotFoundException e) {}
    return null;
}


이미지 처리 성능 향상을 시키기 위해 가장 먼저 이미지의 크기를 확인하자.
앱에서 사용자에게 보여주는 사진 이미지는 매우 작아 해상도가 좋을 필요가 없으므로 서버에서 크기를 작게 만들고 압축률을 변경하면 클라이언트로 전달되는 파일 크기가 작아져 전송속도가 빨라지고, 체감속도가 증가한다.


ImageView의 setImageResource() 메소드 사용을 피하자
이 메소드를 사용하면 이미지를 읽고 디코딩 하는 작업을 UI Thread에서 하기 때문에 응답시간이 느려진다.

setImageDrawable 메소드나 setImageBitmap 메소드를 사용하고, BitmapFactory 클래스를 사용하는 것이 좋다.



이미지 다루는 파일 전체 소스는 CustomListView 게시글(http://link2me.tistory.com/1255)에도 있고 http://www.androidbegin.com/tutorial/android-json-parse-images-and-texts-tutorial/ 에서도 구할 수 있음


이미지 로딩 라이브러리 Glide 사용하면 위와 같은 걸 고민할 필요가 없더라.

http://link2me.tistory.com/1498 참조


블로그 이미지

Link2Me

,
728x90
웹서버 상에 이미지를 다운로드하여 안드로이드 어플에서 사진정보를 보여주기 위한 기능을 테스트를 위해서 구글링을 열심히 하면서 이미지 처리하는 예제를 제대로 하나 찾았다.

http://www.androidbegin.com/tutorial/android-json-parse-images-and-texts-tutorial/

Eclipse 기반에서 만들어진 것이라서 Android Studio 로 Import 해서 기능이 잘 동작하는 걸 확인하고 나서 본격적인 분석에 들어갔다.

지금까지 내가 테스트하고 있는 것의 잘못된 부분이 뭔지 찾기 위해 좀 더 이것저것 테스트를 했고 이제 제대로 된 결과가 나오는 걸 확인했다.

아쉬운 점은 구글링으로 구한 이미지 축소하는 소스가 크기가 일정하지 못하다는 단점이 있다.


검색하면 이미지 캐싱 기능이 되는 소스는 많이 검색된다.

문제는 서버에 있는 이미지를 어떻게 가져다가 활용하는지에 대한 예재를 찾기가 어려웠다.

텍스트 정보만 가져와서 출력하는 기능 구현은 쉽게 가능했다.


PersonData.java

 public class PesonData {
    // PersonData 정보를 담고 있는 객체 생성
    private String profile_image; // 이미지 경로를 String으로 받기 위해서
    private String uid;
    private String name;
    private String mobileNO;
    private String officeNO;

    public PesonData() {
    }

    public PesonData(String profile_image, String uid, String name, String mobileNO, String officeNO) {
        this.profile_image = profile_image;
        this.uid = uid;
        this.name = name;
        this.mobileNO = mobileNO;
        this.officeNO = officeNO;
    }

    public String getProfile_image() {
        return profile_image;
    }

    public void setProfile_image(String profile_image) {
        this.profile_image = profile_image;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobileNO() {
        return mobileNO;
    }

    public void setMobileNO(String mobileNO) {
        this.mobileNO = mobileNO;
    }

    public String getOfficeNO() {
        return officeNO;
    }

    public void setOfficeNO(String officeNO) {
        this.officeNO = officeNO;
    }
}


MainActivity.java

 public class MainActivity extends AppCompatActivity {

    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;

    private ArrayList<PesonData> arrayList = null; // 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter
    ProgressDialog mProgressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = (EditText) findViewById(R.id.et_text01);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        arrayList = new ArrayList<PesonData>(); // ArrayList 생성

        listView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅

        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                //System.out.println("search ==="+ editText.getText().toString().trim());
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","")
                final String postParams = builder.build().getEncodedQuery();

                new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
                hideSoftKeyboard();
            }
        });
    }

    private void hideSoftKeyboard(){ // 소프트 키보드 숨기기
        InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE );
        imm.hideSoftInputFromWindow( editText.getApplicationWindowToken(), 0 );
    }

    class getJSONData extends AsyncTask<String, Void, String> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // Create a progressdialog
            mProgressDialog = new ProgressDialog(MainActivity.this);
            mProgressDialog.setTitle("Personal Profile JSON Parse");
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setIndeterminate(false);
            mProgressDialog.show();  // Show progressdialog
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            searchJSON=result;
            showList();
            mProgressDialog.dismiss();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS="result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "name";
    private static final String TAG_MobileNO ="mobileNO";
    private static final String TAG_OfficeNO ="officeNO";

    private static final String TAG_Image = "photo"; // 이미지 필드

    JSONArray peoples = null;

    protected void showList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            arrayList.clear(); // 서버에서 가져온 데이터 초기화
            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                final String uid = c.getString(TAG_UID);
                final String name = c.getString(TAG_NAME);
                final String mobileNO = c.getString(TAG_MobileNO);
                final String officeNO = c.getString(TAG_OfficeNO);

                final String Profile_Image = c.getString(TAG_Image);


                // 서버에서 가져온 데이터 저장
                listViewAdapter.addItem(photoURL,uid,name,mobileNO,officeNO);
            }

            runOnUiThread(new Runnable() {

                // 화면에 반영하기 위하여 runOnUiThread()를 호출하여 실시간 갱신한다.
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    class ViewHolder {
        public LinearLayout child_layout;
        public ImageView profile_Image;
        public TextView tv_name;
        public TextView tv_mobileNO;
        public TextView tv_officeNO;
        public Button chid_btn;
    }

    private class ListViewAdapter extends BaseAdapter {
        ImageLoader imageLoader;
        Context context;

        public ListViewAdapter(Context context) {
            this.context = context;
            imageLoader = new ImageLoader(context);
        }

        @Override
        public int getCount() {
            return arrayList.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return arrayList.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();

            // 화면에 표시될 View
            if(convertView == null){
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.person_item,parent,false);

                convertView.setBackgroundColor(0x00FFFFFF);
                convertView.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
                viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
                viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
                viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
                viewHolder.chid_btn = (Button) convertView.findViewById(R.id.child_Btn);

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            final PesonData pesonData = arrayList.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            // 선택된 row의 데이터를 표시한다. 표시될 view는 person_item.xml 의 각 항목을 이용 표시한다.

            System.out.println("imageurl==" + pesonData.getProfile_image());
            if(pesonData.getProfile_image().equals("")){
                final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
                viewHolder.profile_Image.setImageBitmap(Base_Profile);
            } else {
                final String photoURL = Value.IPADDRESS + "/photos/" + pesonData.getProfile_image();
                imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
            }

            viewHolder.tv_name.setText(pesonData.getName());
            viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
            viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

            viewHolder.chid_btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                }
            });

            return convertView;
        }

        // 아이템 데이터 추가를 위한 메소드
        public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
            PesonData item = new PesonData();
            item.setProfile_image(profile_image);
            item.setUid(uid);
            item.setName(name);
            item.setMobileNO(mobileNO);
            item.setOfficeNO(officeNO);

            arrayList.add(item);
        }
    }
}


PHPComm.java

 public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {
            Thread.sleep(100);
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            if(conn != null){ // 연결되었으면
                //add request header
                conn.setRequestMethod("POST");
                conn.setRequestProperty("USER-AGENT", "Mozilla/5.0");
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
                if (cookieString != null) {
                   conn.setRequestProperty("Cookie", cookieString);
                   Log.e("PHP_getCookie", cookieString);
                 }
                conn.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);
                conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                conn.setDoInput(true);

                // Send post request
                DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                wr.writeBytes(postParams);
                wr.flush();
                wr.close();

                int responseCode = conn.getResponseCode();
                System.out.println("GET Response Code : " + responseCode);        
                if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                    bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out : " + sb.toString());
            return sb.toString().trim();
            // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }
    }

    public static boolean isExists(String URLName) {
        // 서버에 파일의 존재 유무 파악
        try {
            HttpURLConnection.setFollowRedirects(false);
            HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
            con.setRequestMethod("HEAD");
            if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    public static Bitmap autoresize_decodeResource(Resources res, int resId, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInAutoSize(options, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInAutoSize(BitmapFactory.Options options, int reqHeight) {
        // 원본 이미지의 높이와 너비
        final int height = options.outHeight;
        final int width = options.outWidth;

        float ratio = width / height;
        int reqWidth = Math.round((float) ratio * width);

        int inSampleSize = 1;
        if (height > reqHeight) {
            final int halfHeight = height / 2;
            while ((halfHeight / inSampleSize) > reqHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}



블로그 이미지

Link2Me

,
728x90

웹서버 상에 이미지를 다운로드하여 안드로이드 어플에서 사진정보를 보여주기 위한 기능을 테스트 중이다.


먼저 웹서버에 이미지 파일이 존재하는지 유무를 체크하는 기능부터 테스트를 했다.


protected void showList() {
    try {
        JSONObject jsonObj = new JSONObject(searchJSON);
        peoples = jsonObj.getJSONArray(TAG_RESULTS);

        personDataItem.clear(); // 서버에서 가져온 데이터 초기화
        for(int i=0;i<peoples.length();i++){
            JSONObject c = peoples.getJSONObject(i);
            final String uid = c.getString(TAG_UID);
            String name = c.getString(TAG_NAME);
            String mobileNO = c.getString(TAG_MobileNO);
            String officeNO = c.getString(TAG_OfficeNO);
            Drawable myIcon = getResources().getDrawable(R.mipmap.ic_launcher);

            final String photoURL = Value.IPADDRESS + "/photos/" + uid + ".jpg";
            System.out.println("Server Photo URL ===" + photoURL);
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    if(isExists(photoURL) == true){
                        System.out.println("Server Photo Image ===" + uid);
                    }
                }
            });

            // 서버에서 가져온 데이터 저장
            listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 갱신된 데이터 내역을 어댑터에 알려줌
                listViewAdapter.notifyDataSetChanged();
            }
        });

    } catch (JSONException e) {
        e.printStackTrace();
    }
}
 

 private static boolean isExists(String URLName) {
    // 서버에 파일의 존재 유무 파악
    try {
        HttpURLConnection.setFollowRedirects(false);
        HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
        con.setRequestMethod("HEAD");
        if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
    return false;
}


for 문 내에서 순차적으로 결과가 나올 것 같지만 AsyncTask.execute(new Runnable() 에서 처리는 나중에 처리됨을 확인할 수 있다.


사진 이미지가 있는 경우와 없는 경우 결과가 false 로 처리됨을 확인할 수 있다.


코드 구현시 결과가 순차적으로 실행되지 않는다는 점이 중요한 이슈가 될 수도 있다.

순서대로 처리되어야 할 로직인 경우에는 구현 로직을 고민해봐야 한다.


이제 서버에서 파일을 안드로이드폰으로 다운로드하는 함수를 만들어야 한다.

protected void showList() {
    try {
        JSONObject jsonObj = new JSONObject(searchJSON);
        peoples = jsonObj.getJSONArray(TAG_RESULTS);

        personDataItem.clear(); // 서버에서 가져온 데이터 초기화
        for(int i=0;i<peoples.length();i++){
            JSONObject c = peoples.getJSONObject(i);
            final String uid = c.getString(TAG_UID);
            final String name = c.getString(TAG_NAME);
            final String mobileNO = c.getString(TAG_MobileNO);
            final String officeNO = c.getString(TAG_OfficeNO);
            // 이미지 사이즈가 커서 생기는 문제??
            final Bitmap myIcon = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);

            final String photoURL = Value.IPADDRESS + "/photos/" + uid + ".jpg";
            System.out.println("Server Photo URL ===" + photoURL);
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    if(isExists(photoURL) == true){
                        Bitmap rBmp = loadWebImage(photoURL);
                        System.out.println("Server Photo Image ==="+ uid +" | "+ rBmp);
                        System.out.println("Photo Image height ==="+ rBmp.getHeight());
                        int width = (int)(rBmp.getWidth() * 160.0 / rBmp.getHeight());
                        rBmp = Bitmap.createScaledBitmap(rBmp, width, 160,true);

                        // 서버에서 가져온 데이터 저장
                        listViewAdapter.addItem(rBmp,uid,name,mobileNO,officeNO);
                    } else {
                        // 서버에 사진 이미지가 없으면 기본 이미지를 포함한 정보 저장
                        listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
                    }
                }
            });

            // 서버에서 가져온 데이터 저장
            //listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 갱신된 데이터 내역을 어댑터에 알려줌
                listViewAdapter.notifyDataSetChanged();
            }
        });

    } catch (JSONException e) {
        e.printStackTrace();
    }
}

private static boolean isExists(String URLName) {
    // 서버에 파일의 존재 유무 파악
    try {
        HttpURLConnection.setFollowRedirects(false);
        HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
        con.setRequestMethod("HEAD");
        if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
    return false;
}

Bitmap mBmp = null;
// 서버에서 전달 받은 데이터를 Bitmap 이미지에 저장
public Bitmap loadWebImage(String URLName) {
    try {
        // 스트림 데이터를 Bitmap 에 저장
        InputStream is = new URL(URLName).openStream();
        mBmp = BitmapFactory.decodeStream(is);
        is.close();
    } catch(Exception e) {
        Log.d("tag", "Image Stream error.");
        return null;
    }
    return mBmp;
}

 public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {  
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            if(conn != null){ // 연결되었으면
                //add request header
                conn.setRequestMethod("POST");
                conn.setRequestProperty("USER-AGENT", "Mozilla/5.0");
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
                if (cookieString != null) {
                   conn.setRequestProperty("Cookie", cookieString);
                   Log.e("PHP_getCookie", cookieString);
                 }
                conn.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);
                conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                conn.setDoInput(true);

                // Send post request
                DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                wr.writeBytes(postParams);
                wr.flush();
                wr.close();

                int responseCode = conn.getResponseCode();
                System.out.println("GET Response Code : " + responseCode);        
                if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                    bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out : " + sb.toString());
            return sb.toString().trim();  

        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }
    }

    public static Bitmap autoresize_decodeResource(Resources res, int resId, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInAutoSize(options, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInAutoSize(BitmapFactory.Options options, int reqHeight) {
        // 원본 이미지의 높이와 너비
        final int height = options.outHeight;
        final int width = options.outWidth;

        float ratio = width / height;
        int reqWidth = Math.round((float) ratio * width);

        int inSampleSize = 1;
        if (height > reqHeight) {
            final int halfHeight = height / 2;
            while ((halfHeight / inSampleSize) > reqHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}


현재까지 테스트에 사용되었던 파일을 첨부한다.

이미지 테스트를 하면서 Bitmap 가져오기를 하면서 PersonData.java 파일 내용도 수정되었다.


json_demo_getImage.zip



이미지 사이즈를 줄이는 것까지는 성공했는데, 문제는 매번 접속할 때마다 새롭게 이미지 파일을 받아오는 문제점을 해결해야 한다.

http://theeye.pe.kr/archives/1309

http://www.androidbegin.com/tutorial/android-json-parse-images-and-texts-tutorial/

에 좋은 내용의 예제가 있다. 이것을 이용하여 코드를 다시 수정하고 테스트를 해봐야겠다.


블로그 이미지

Link2Me

,
728x90

ListViewAdapter 를 만드는 과정이다.

Android Studio 가 제공하는 자동완성 기능을 이용하여 기본적인 골격을 만들어보자.




private class ListViewAdapter extends BaseAdapter {

    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        return null;
    }
}


기본 코드가 만들어진다. 

연결된 정보들이 없어서 return null, return 0 를 반환하는 걸로 기본 코드가 만들어진 것을 확인할 수 있다.

이 코드에 살을 붙여서 완성된 코드를 만들어야 한다.


리스트뷰는 일반 위젯(TextView등)이 아니라 선택 위젯이기 때문에 직접 데이터를 설정할수가 없다.
Adapter를 이용해야 하고, 이 Adapter에서 만들어주는 getView()를 이용해서 아이템을 표시한다.
리스트뷰는 어댑터를 사용하여 데이터를 표시하는 View이다.




먼저 MainActivity Class 전역변수를 선언한다.

private ArrayList<PesonData> personDataItem = null; // 데이터 리스트
private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter


class ViewHolder {
    public LinearLayout child_layout;
    public ImageView personal_imageView;
    public TextView tv_name;
    public TextView tv_mobileNO;
    public TextView tv_officeNO;
    public Button chid_btn;
}

private class ListViewAdapter extends BaseAdapter {

    public ListViewAdapter() {
    }

    @Override
    public int getCount() {
        return personDataItem.size(); // 데이터 개수 리턴
    }

    @Override
    public Object getItem(int position) {
        return personDataItem.get(position); // 지정한 위치(position)에 있는 데이터를 리턴
    }
   
    @Override
    public long getItemId(int position) {
        return position; 
// 지정한 위치(position)에 있는 데이터 리턴
    }

    // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        final Context context = viewGroup.getContext();

        // 화면에 표시될 View
        if(view == null){
            viewHolder = new ViewHolder();

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.person_item,viewGroup,false);

            view.setBackgroundColor(0x00FFFFFF);
            view.invalidate();

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.personal_imageView = (ImageView) view.findViewById(R.id.personal_Image);
            viewHolder.tv_name = (TextView) view.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) view.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) view.findViewById(R.id.child_officeNO);
            viewHolder.chid_btn = (Button) view.findViewById(R.id.child_Btn);

            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        PesonData pesonData = personDataItem.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        viewHolder.personal_imageView.setImageDrawable(pesonData.getIcon());
        viewHolder.tv_name.setText(pesonData.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

        viewHolder.chid_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });

        return view;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(Drawable icon, String uid, String name, String mobileNO, String officeNO){
        PesonData item = new PesonData();
        item.setIcon(icon);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        personDataItem.add(item);
    }
}
 


이제 전체 코드를 보자.

안드로이드가 기본 제공하는 getView() 와 비교할 수 있는 부분은 별도 색상으로 표기를 했다.


import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.telephony.PhoneNumberUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;

    private ArrayList<PesonData> personDataItem = null; // 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.my_listView);
        editText = (EditText) findViewById(R.id.et_text01);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        personDataItem = new ArrayList<PesonData>(); // ArrayList 생성

        // Adapter 생성
        listViewAdapter = new ListViewAdapter();

        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
       
        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","")
                String postParams = builder.build().getEncodedQuery();
                new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
            }
        });
    }
   
    class getJSONData extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            searchJSON=result;
            showList();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS="result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "name";
    private static final String TAG_MobileNO ="mobileNO";
    private static final String TAG_OfficeNO ="officeNO";
    JSONArray peoples = null;

    protected void showList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            personDataItem.clear(); // 서버에서 가져온 데이터 초기화
            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                String uid = c.getString(TAG_UID);
                String name = c.getString(TAG_NAME);
                String mobileNO = c.getString(TAG_MobileNO);
                String officeNO = c.getString(TAG_OfficeNO);
                Drawable myIcon = getResources().getDrawable(R.mipmap.ic_launcher); // 이미지는 임시처리

                // 서버에서 가져온 데이터 저장
                listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
            }

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    class ViewHolder {
        public LinearLayout child_layout;
        public ImageView personal_imageView;
        public TextView tv_name;
        public TextView tv_mobileNO;
        public TextView tv_officeNO;
        public Button chid_btn;
    }

    private class ListViewAdapter extends BaseAdapter {

        public ListViewAdapter() {
        }

        @Override
        public int getCount() {
            return personDataItem.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return personDataItem.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(int position, View view, ViewGroup viewGroup) {
            ViewHolder viewHolder;
            final Context context = viewGroup.getContext();

            // 화면에 표시될 View
            if(view == null){
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.person_item,viewGroup,false);

                view.setBackgroundColor(0x00FFFFFF);
                view.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.personal_imageView = (ImageView) view.findViewById(R.id.personal_Image);
                viewHolder.tv_name = (TextView) view.findViewById(R.id.child_name);
                viewHolder.tv_mobileNO = (TextView) view.findViewById(R.id.child_mobileNO);
                viewHolder.tv_officeNO = (TextView) view.findViewById(R.id.child_officeNO);
                viewHolder.chid_btn = (Button) view.findViewById(R.id.child_Btn);

                view.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) view.getTag();
            }

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            PesonData pesonData = personDataItem.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            viewHolder.personal_imageView.setImageDrawable(pesonData.getIcon());
            viewHolder.tv_name.setText(pesonData.getName());
            viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
            viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

            viewHolder.chid_btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                }
            });

            return view;
        }

        // 아이템 데이터 추가를 위한 메소드
        public void addItem(Drawable icon, String uid, String name, String mobileNO, String officeNO){
            PesonData item = new PesonData();
            item.setIcon(icon);
            item.setUid(uid);
            item.setName(name);
            item.setMobileNO(mobileNO);
            item.setOfficeNO(officeNO);

            personDataItem.add(item);
        }
    }

}
 


잘 정리된 블로그를 참조하면서 테스트 해보고 내 스타일로 수정하고 컴파일 하면서 완성도를 높여가며 테스트중이다. 실제 서버와 연동하여 실제 활용할 수 있는 코드 중심으로 작성해보는 중이다.


ListViewAdapter 를 Activity Class 단위로 생성해서 사용하는 것이 다른 Class 파일을 만드는 것보다 이해도가 좋은 거 같다.


추가 테스트가 필요한 사항

- 사진 이미지를 가져와서 보여주는 것 해결사항

- 버튼 이미지 또는 이미지를 누르면 Event 처리하는 기능


첨부 코드는 http://link2me.tistory.com/1250 파일에서 달라진 파일만 첨부했다.


json_demo_customListView.zip


블로그 이미지

Link2Me

,
728x90

simple_list_item_1 arrayAdapter는 아이템이 TextView만으로 구성되고 데이터가 String 배열이기 때문에 ArrayAdapter 만으로 그 기능을 제공할 수 있다.

Custom ListView 를 제공하기 위해서는 xml 을 정의하고 ArrayAdapter 기능을 확장해야 한다.


사용자 정의형 ListView 에 뿌려질 xml 를 정의하고, Inflation 해주어야 한다.

 item 을 위한 xml 정의(Layout)
 사용자에게 보여질 xml 를 만들고 나서, 실제 Data를 연결할 Class 를 정의한다.
 person_item.xml 과 PersonData.java 파일을 생성한다. 


먼저, layout 폴더에 person_item.xml 파일을 추가한다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/child_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp" >

    <ImageView
        android:id="@+id/mImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@mipmap/ic_launcher"
        android:layout_weight="1"
        />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_weight="4"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="2dip"
            android:text="이름"
            android:textSize="20sp"
            android:textStyle="bold"
            />

        <TextView
        android:id="@+id/mobileNO"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:text="휴대폰번호"
        android:textStyle="bold"
        />

        <TextView
            android:id="@+id/officeNO"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text="휴대폰번호"
            android:textStyle="bold"
            />

    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_weight="1"
        android:orientation="vertical" >

        <Button
            android:id="@+id/childListBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center_horizontal|center_vertical"
            android:text="Btn"
            android:textAllCaps="false"
            />
    </LinearLayout>
</LinearLayout>



PersonData.java Class 파일을 생성한다.

그리고 변수를 추가한다.

public class PesonData {
    // PersonData 정보를 담고 있는 객체 생성
    private int profile_image;
    private String uid;
    private String name;
    private String mobile;

    public PesonData(){  // 생성자
    }


변수가 private으로 되어있으므로 외부에서 접근할수있도록 메소드를 만들어주어야 한다.

데이터 없는 생성자 다음 빈공간에서 Alt + Insert 키를 누르고, Gatter and Setter 를 선택하면 자동으로 코드가 추가된다.

다음으로 Generator 를 생성하기 위해 Alt + Insert 키를 누르고 아래 그림처럼 따라 한다.


추가하면 위와 같이 나온다.

이 파일에 대한 전체 코드는 아래와 같다.


public class PesonData {
    // PersonData 정보를 담고 있는 객체 생성
    private int profile_image;
    private String uid;
    private String name;
    private String mobile;

    public PesonData(){  // 생성자
    }

    public PesonData(int profile_image, String uid, String name, String mobile) {
        this.profile_image = profile_image;
        this.uid = uid;
        this.name = name;
        this.mobile = mobile;
    }

    public int getProfile_image() {
        return profile_image;
    }

    public void setProfile_image(int profile_image) {
        this.profile_image = profile_image;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}
 


내용을 구성하는 PersonData Class까지 작성했다.

코드는 컴파일하면서 에러가 발생하면 다시 수정하면서 작성해서 명칭 등이 약간 변경되었다.

실제 코드를 ListViewAdapter 만드는 방법 게시글(http://link2me.tistory.com/1252)에 올려두었다.


블로그 이미지

Link2Me

,
728x90

서버에서 데이터를 가져오는 방법은 http://link2me.tistory.com/1247 참조하면 된다.

실제 파일을 연습해볼 수 있는 샘플을 첨부한다. 첨부파일은 링크 게시물에 올려진 파일과는 다르고 아래 기술되는 내용의 연속성 측면에서 새롭게 작성한 파일이다. 이 파일을 기반으로 수정해서 ListData 를 만들것이다.


json_demo_getServerData.zip

json_demo_server_PHP.zip



이제 서버에서 가져온 데이터를 ArrayList 에 추가하는 방법에 대해 알아보자.


private ArrayList<String> personData = new ArrayList<String>();

로 선언을 해준다.

그리고 기본 ArrayAdapter 를 통해 ListView 에 뿌려준다.


activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/et_text01"
            android:hint="검색어"
            android:layout_weight="8"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/btn_search"
            android:text="검색"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <ListView
        android:id="@+id/lv_listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

 MainActivity.java

 import android.app.Activity;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;

    private ArrayList<String> personData = null; // 데이터 리스트
    private ArrayAdapter<String> arrayAdapter = null; // 리스트뷰에 사용되는 ArrayAdapter

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.lv_listView);
        editText = (EditText) findViewById(R.id.et_text01);

        personData = new ArrayList<String>(); // ArrayList 생성

        // ArrayAdapter 생성
        arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,personData);

        listView.setAdapter(arrayAdapter); // 어댑터를 리스트뷰에 세팅

        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","")
                String postParams = builder.build().getEncodedQuery();
                new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
            }
        });
    }

    class getJSONData extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            searchJSON=result;
            showList();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS="result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "name";
    private static final String TAG_Mobile ="mobileNO";
    JSONArray peoples = null;

    protected void showList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            personData.clear(); // 서버에서 가져올 때마다 초기화

            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                String uid = c.getString(TAG_UID);
                String name = c.getString(TAG_NAME);
                String mobile = c.getString(TAG_Mobile);

                String data = uid + " : " + name + " | " + mobile;
                personData.add(data); // 데이터 추가
            }

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    arrayAdapter.notifyDataSetChanged();
                }
            });

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
}


위 코드는 ListView에 단순한 형태로 뿌려주는 기능만 테스트를 했다.


ListView Row를 눌렀을 때 이벤트를 거는 걸 처리해보자.

보통 AdapterView.onItemClickListener 를 이용하여, 이벤트 처리를 해준다.

onItemClickListener는 CallBack Listener 다. CallBack 이란 ListView의 한 Row(행)을 클릭했을 때, 시스템에서 클릭에 반응하여 다시 사용자에게 알려주는 것을 말한다.
ListView의 Row 를 클릭했을 때 그 Row에 해당하는 adapterView, view, position 등을 시스템에서 사용자에게 전달해주는 것이다.

listView.setOnItemClickListener(itemClickListener); // 이벤트 처리

// ListView에서 아이템을 누르면 반응하는 이벤트이기때문에 onItemClickListener를 ListView에 추가

private AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
        String textView = (String) adapterView.getAdapter().getItem(position);
        Toast.makeText(MainActivity.this, textView, Toast.LENGTH_SHORT).show();

        Toast.makeText(getApplicationContext(), "" + position, Toast.LENGTH_SHORT).show();
    }
};


position 은 선택한 Row의 index 값을 반환한다.



이미지, 텍스트, 버튼을 혼용하는 View 를 ListView 에 보여주는 방법은 많은 테스트를 해보고 작성하련다.

ArrayList<String> 형을 ArrayList<PersonData> 제네릭 형으로 변경하고, 데이터를 add 하면 에러가 발생한다.


private ArrayList<String> personData = null; // 데이터 리스트
private ArrayAdapter<String> arrayAdapter = null; // 리스트뷰에 사용되는 ArrayAdapter

 private ArrayList<PersonData> personData = null; // 데이터 리스트
private ArrayAdapter<PersonData> arrayAdapter = null; // 리스트뷰에 사용되는 ArrayAdapter


arrayAdapter = new ArrayAdapter<PersonData>(this,android.R.layout.simple_list_item_1,personData); AdapterView 에서 처리를 못해서 생기는 에러로 보인다.

블로그 이미지

Link2Me

,
728x90

ListView는 사용자가 정의한 데이터 목록을 아이템 단위로 구성하여 화면에 출력하는 ViewGroup의 한 종류다.
즉, ListView 는 어댑터(Adapter)를 통해 데이터를 뿌려주는 형식의 View 다.

ListView는 항목들을 수직으로 보여주는 어댑터 뷰로 상하로 스크롤이 가능하다.

ListView에 표시되는 아이템은 단순히 Text만 출력하는 구조가 될 수 있고, Image, Button, CheckBox 등 여러 View의 조합으로 구성되는 좀 더 복잡한 형태(Custom)가 될 수도 있다.

가장 간단하게 텍스트만 보여주는 예제를 살펴보자.

번호 1, 번호2, 번호 3과의 관계가 어떻게 되는지 아래 코드에서 보면된다.


 3번 화면에 출력할 ListView 추가 (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/istview_01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

 1번 배열 리스트 생성 및 2번 어댑터 생성, ListView에 어댑터 연결

 public class MainActivity extends AppCompatActivity {

    ArrayList<String> arrayList = null;
    ArrayAdapter<String> adapter = null;
    ListView listView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 데이터 배열 리스트 생성
        arrayList = new ArrayList<String>();
        arrayList.add("이순신");
        arrayList.add("홍길동");
        arrayList.add("유관순");
        arrayList.add("강감찬");
        arrayList.add("김유신");
        arrayList.add("강길덕");

        // 어댑터 생성 (안드로이드에서 기본 제공하는 어댑터 활용)
        adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,arrayList);

        // AdapterView
        listView = (ListView) findViewById(R.id.istview_01);
        listView.setAdapter(adapter); // ListView 에 어댑터 설정
    }
}


이렇게 데이터 배열 리스트를 생성하는 경우는 없지만 개념으로 알아두자.


ArrayAdapter(Context context, int textViewResourceID, 배열);

- context : 현재 어플리케이션 컨텍스트

- int textViewResourceID : 레이아웃 아이디


ArrayAdapter 클래스는 제네릭 클래스로 정의되어 있다.

android.R.layout.simple_list_item_1 은 안드로이드 시스템 내부에 미리 만들어진 리소스중 하나로 SDK를 설치하면 자동으로 설치되며 어떠한 추가 작업 없이 개발자가 바로 사용할 수 있다.

 android.R.layout.simple_list_item_1

 하나의 텍스트 뷰 사용

 android.R.layout.simple_list_item_checked

 항목당 체크 표시

 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE)

 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE)



이제 데이터 리스트 배열을 만드는 방법을 바꿔보자.


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="contact">
        <item>김유신 010-0001-00001</item>
        <item>강감찬 010-0001-00002</item>
        <item>이순신 010-0001-00003</item>
        <item>유관순 010-0001-00004</item>
        <item>홍길동 010-0001-00005</item>
        <item>사임당 010-0001-00006</item>
        <item>최영 010-0001-00007</item>
        <item>이성계 010-0001-00008</item>
        <item>이방원 010-0001-00009</item>
        <item>정몽주 010-0001-00010</item>
        <item>김태성 010-0001-00011</item>
        <item>강길덕 010-0001-00012</item>
    </string-array>
</resources>


 public class MainActivity extends AppCompatActivity {

    ArrayAdapter<CharSequence> adapter = null;
    ListView listView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        adapter = ArrayAdapter.createFromResource(
                                this,R.array.contact,android.R.layout.simple_list_item_checked);

        listView = (ListView) findViewById(R.id.listview_01);

        listView.setAdapter(adapter);
        listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        ColorDrawable colorDrawable = new ColorDrawable(Color.RED);
        listView.setDivider(colorDrawable);
        listView.setDividerHeight(3);
    }
}


블로그 이미지

Link2Me

,