728x90

Wrapper 클래스 : 8개의 기본형을 객체로 다뤄야 할 때 사용하는 클래스

Wrapper classes provide a way to use primitive data types (int, boolean, etc..) as objects.

 

wrapper의 사전적 의미는 '(특히 식품) 포장지'라는 뜻이다.
기본 자료형에 대해서 객체로서 인식되도록 '포장'했다는 의미이다.

 

기본형 타입 변수는 소문자로 시작되는데 Wrapper 클래스 변수는 대문자로 시작된다.

이중에서 Integer 와 Character 래퍼 클래스는 기본형과 형태가 다르고 나머지는 첫글자 대/소문자만 다르다.

 

ArrayList<int> myNumbers = new ArrayList<int>(); // Invalid
ArrayList<Integer> myNumbers = new ArrayList<Integer>(); // Valid

 

자바 1.5 버전 이상부터는 박싱/언박싱이 자동으로 되므로 크게 신경쓸 필요는 없다.

 

사용하는 이유

1. 기본 데이터 타입을 Object로 변환할 수 있다.

2. java.util 패키지의 클래스는 객체만 처리하므로 Wrapper class는 이 경우에도 도움이 된다.

3. ArrayList 등과 같은 Collection Framework의 데이터 구조는 기본 타입이 아닌 객체만 저장하게 되고, Wrapper class를 사용하여 자동박싱/언박싱이 일어난다.
4. 멀티쓰레딩에서 동기화를 지원하려면 객체가 필요하다.

 

예제

import java.util.ArrayList;
 
public class WrapperClass {
    public static void main(String[] args) {
        // Wrapper 클래스 : 8개의 기본형을 객체로 다뤄야 할 때 사용하는 클래스
        // JDK 1.5 이전에는 기본형과 참조형간 연산이 불가능
        // Autoboxing은 자바 컴파일러가 primitive data type을
        // 그에 상응하는 wrapper class 로 자동 변환시켜주는 것을 의미
        // Unboxing 은 자바 컴파일러가 wrapper class 를 primitive data type으로 자동 변환시켜 주는 것
 
        Integer num = 17// Auto Boxing : Integer num = new Integer(17);
        // Boxing : 기본 타입의 데이터를 wrapper class의 인스턴스로 변환하는 과정 Integer num = new Integer(1);
        // wrapper class 인스턴스에 저장된 값을 변경할 수 없다.(산술 연산을 위한 클래스가 아니기 때문) ???
        int n = num; // Unboxing : int n = num.intValue();
        // 언박싱(UnBoxing) : wrapper class의 인스턴스에 저장된 값을 -> 기본 타입의 데이터로 꺼내는 과정
        // JDK 1.5부터 boxing,unboxing이 필요한 상황에서 자바 컴파일러가 이를 자동으로 처리해준다.
        System.out.println(n);
 
        // Wrapper Class 비교연산 (==, equals)
        Integer num1  = new Integer(100); // 0X100 주소에 100이 저장되어 있다고 하자.
        Integer num2 = new Integer(100); // 0X200 주소에 100이 저장되어 있다고 하자.
        int i = 100//기본타입
 
        System.out.println("num1==num2 ? "+(num1==num2)); // 참조주소 비교 (false)
        System.out.println("num1.equals(num2) ? "+num1.equals(num2)); // 내용(값) 비교 (true)
        System.out.println("num1.compareTo(num2)="+num1.compareTo(num2)); // 같으면 0,우측이 작으면 양수
        System.out.println("num1.toString()="+num1.toString()); // 문자열
        // 래퍼 클래스와 기본자료형과의 비교는 == 연산과 equals연산 모두 가능하다.
        // 컴파일러가 자동으로 오토박싱과 언박싱을 해주기 때문이다.
        System.out.println("래퍼클래스 == 기본타입 : "+(num1 == i)); // true
        System.out.println("래퍼클래스.equals(기본타입) : "+(num1.equals(i))); // true
 
        System.out.println("-----------------------");
        // 래퍼 객체에 상응하는 값을 얻기 위해 사용
        // intValue(), byteValue(), shortValue(), longValue(),
        // floatValue(), doubleValue(), charValue(), booleanValue().
        Integer myInt = 5;
        Double myDouble = 5.99;
        Character myChar = 'A';
        System.out.println(myInt.intValue());
        System.out.println(myDouble.doubleValue());
        System.out.println(myChar.charValue());
 
        System.out.println("-----------------------");
        // 문자열을 숫자로 변환하기
        int        i1  = new Integer("100").intValue();
        int        i3 = Integer.parseInt("100");
        Integer  i4 = Integer.valueOf("100");
 
        int i5 = Integer.parseInt("100",2);
        int i6 = Integer.parseInt("100",8);
        int i7 = Integer.parseInt("100",16);
        int i8 = Integer.parseInt("FF"16);
 
        System.out.println(Integer.parseInt("100"2));
        System.out.println(Integer.parseInt("100"8));
        System.out.println(Integer.parseInt("100"10));
        System.out.println(Integer.parseInt("100"16));
        System.out.println(Integer.parseInt("FF"16));
 
        System.out.println("-----------------------");
        //ArrayList<int> myNumbers = new ArrayList<int>(); // Invalid
        // Generics 타입에 primitive data types는 사용 불가.
        ArrayList<Integer> list = new ArrayList<>();
        list.add(new Integer(100)); // 리스트에는 객체만 추가 가능
        list.add(100); // JDK 1.5 이전에는 에러. Autoboxing
 
        Integer ii = list.get(0); // list 에 저장된 첫번째 객체를 꺼낸다.
        int jj = list.get(0); // intValue()로 Integer를 int로 변환
 
        System.out.println(list.getClass().getName()); // 객체 Type 확인
        System.out.println(ii.getClass().getName()); // 객체 Type 확인
        System.out.println(list.get(0));
    }
}
 

 

'안드로이드 > Java 문법' 카테고리의 다른 글

[Java] Stream 예제 2  (0) 2021.11.16
[Java ] Stream 예제  (0) 2021.11.15
[Java] Generics 예제1  (2) 2021.11.10
HashMap 예제  (0) 2021.10.30
[Java] HashSet 를 이용한 ArrayList 중복제거 및 정렬  (0) 2020.12.12
블로그 이미지

Link2Me

,
728x90

이 내용은 다른 블로그에 있는 걸 거의 그대로 베껴서 코드가 동작하는 걸 확인해보고 적어둔다.

문제

//member.csv 파일 일부 
이름, 취미, 소개 
김프로, 축구:농구:야구, 구기종목 좋아요 
정프로, 개발:당구:족구, 개발하는데 뛰긴 싫어 
앙몬드, 피아노, 죠르디가 좋아요 좋아좋아너무좋아 
죠르디, 스포츠댄스:개발, 개발하는 죠르디
정미애, 노래:스포츠댄스:개발, 이선희 모창가수 좋아요
 

1. CSV 파일을 읽어서 취미별 인원수를 구하라.

2. CSV 파일을 읽어서 취미별 정씨성을 가진 멤버 수를 구하라.

3. CSV 파일을 읽어서 소개 내용에 '좋아'가 몇번 등장하는 지 카운트하라.

 

 

코드 구현 예제

import java.io.*;
import java.util.*;
 
public class StreamEX6 {
    public static void main(String[] args) {
        // 코드 출처 : https://jeong-pro.tistory.com/212 (동작하는 코드로 작성)
        StreamEX6 st = new StreamEX6();
        st.printMemberCountGroupByHobby();
        System.out.println("======================");
        st.printMemberCountGroupByJeongMember();
        System.out.println("======================");
        st.printLikeCount();
    }
 
    public void printMemberCountGroupByHobby() { // 취미별 인원수 구하기
        List<List<String>> members = readCSV();
 
        members.remove(0); //첫줄 제거
        //System.out.println(members); // 출력하여 확인
 
        Map<String, Integer> result = new HashMap<>(); //결과를 담을 해시맵 구성
        members.stream()
                .flatMap(line -> Arrays.stream(line.get(1).replace(" ","").split(":")))
                //.peek(s->System.out.printf("hobby=%s%n",s))
                .forEach(hobby -> result.merge(hobby, 1, (oldValue, newValue)->++oldValue));
        //출력
        result.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));
    }
 
    public void printMemberCountGroupByJeongMember(){ // 취미별 정씨 성을 갖는 멤버 수
        List<List<String>> members = readCSV();
 
        members.remove(0); //첫줄 제거
        //System.out.println(members);
 
        Map<String, Integer> result = new HashMap<>(); //결과를 담을 해시맵 구성
        members.stream()
                .filter(line->line.get(0).startsWith("정"))
                .flatMap(line -> Arrays.stream(line.get(1).replace(" ","").split(":")))
                //.peek(s->System.out.printf("hobby=%s%n",s))
                .forEach(hobby -> result.merge(hobby, 1, (oldValue, newValue)->++oldValue));
        //출력
        result.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));
    }
 
    public void printLikeCount(){ // 소개 내용에 '좋아'가 몇번 등장하는지 구하기
        List<List<String>> members = readCSV();
 
        members.remove(0); // 첫줄 제거
        //System.out.println(members);
 
        final String word = "좋아";
        int result = members.stream()
                .map(line -> countFindString(line.get(2),word))
                .reduce(0,Integer::sum);
 
        // 출력
        System.out.println(word + " " + result);
    }
 
    private int countFindString(String source, String target){
        int idx = source.indexOf(target);
        if(idx == -1){
            return 0;
        } else {
            return 1 + countFindString(source.substring(idx+1), target);
        }
    }
 
 
    public static List<List<String>> readCSV() {
        List<List<String>> csvList = new ArrayList<List<String>>();
        File csv = new File("c:\\Workspace\\Java\\StreamEX\\src\\members.csv"); // 절대 경로
        BufferedReader reader = null;
        String line = "";
 
        try {
            reader = new BufferedReader(new FileReader(csv));
            while ((line = reader.readLine()) != null) { // readLine()은 파일에서 개행된 한 줄의 데이터를 읽어온다.
                List<String> tmpList = new ArrayList<String>();
                String[] lineArr = line.split(","); // 파일의 한 줄을 ,로 나누어 배열에 저장 후 리스트로 변환한다.
                tmpList = Arrays.asList(lineArr); // 배열을 리스트로 변환
                csvList.add(tmpList);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close(); // 사용 후 BufferedReader를 닫아준다.
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
        //System.out.println(csvList); // 읽어들인 파일의 리스트 확인
        return csvList;
    }
 
}
 

 

 

'안드로이드 > Java 문법' 카테고리의 다른 글

[java] Wrapper Class  (0) 2021.11.18
[Java ] Stream 예제  (0) 2021.11.15
[Java] Generics 예제1  (2) 2021.11.10
HashMap 예제  (0) 2021.10.30
[Java] HashSet 를 이용한 ArrayList 중복제거 및 정렬  (0) 2020.12.12
블로그 이미지

Link2Me

,
728x90

Stream 이란 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것이다.

- 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않는다.

- 스트림은 Iterator 처럼 1회용이다. (필요하면 다시 스트림을 생성해야 함)

- 람다식으로 처리할 수 있도록 도와주는 반복자이다. (JDK 1.8 이상)

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
 
public class StreamEX1 {
    public static void main(String[] args) {
        // Arrays.asList : 배열을 리스트로 바꾸기
        List<Integer> list = Arrays.asList(1,2,3,8,9,10,4,5,6,7);
        System.out.println(list);
 
        // Stream : 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
        // ① 스트림 만들기  ② 중간 연산  ③ 최종 연산
        // 스트림은 데이터 소스로부터 데이터를 읽기만 한다. (변경 하지 않음)
        // 스트림은 Iterator처럼 1회용이다.
        Stream<Integer> stream = list.stream(); // list를 stream 으로 변환
        stream.forEach(System.out::print);
 
        System.out.println();
 
        Stream<Integer> stream2 = list.stream();
        stream2.sorted().map(String::valueOf).forEach(i-> System.out.print(i+":"));
        // 원하지 않는 결과를 출력한다.
 
        System.out.println();
 
        final String result = list.stream().
                sorted().
                map(String::valueOf).
                collect(Collectors.joining(":")); // collect와 forEach 동시 사용 불가
        System.out.println(result);
 
        System.out.println();
        System.out.println("********** 로또 번호 **************");
 
        // 로또번호 출력
        // IntStream : 오토박싱 & 언박싱의 비효율이 제거됨.
        // Stream<Integer> 대신에 IntStream 사용
        IntStream intStream = new Random().ints(1,46);
        final String lotto_result = intStream.distinct().limit(6).sorted()
                .mapToObj(String::valueOf).collect(Collectors.joining(","));
        System.out.println(lotto_result);
 
        // ********************************************************************
        System.out.println();
        System.out.println("******* String.valueof ***********");
        // String.valueof
        Map<String,Object> map = new HashMap<>();
 
        map.put("param1""3번");
        map.put("param2"2);
        map.put("param3"null);
 
        System.out.println(String.valueOf(map.get("param1")));
        System.out.println(String.valueOf(map.get("param2")));
        System.out.println(String.valueOf(map.get("param3"))); // null 반환
        System.out.println(map.get("param3")); // null 반환
        //System.out.println(map.get("param3").toString()); // 에러 발생
 
        for(Map.Entry<String,Object> entry : map.entrySet()) {
            System.out.println("Key : " + entry.getKey());
            System.out.println("Value : " + entry.getValue());
        }
    }
}
 

 

결과

1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10

 

 

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
 
public class StreamEX2 {
    public static void main(String[] args) {
        // Arrays.asList : 배열을 리스트로 바꾸기
        List<String> list = Arrays.asList("홍길동""이순신""강감찬");
        System.out.println(list);
 
        // Iterator 이용 (Java 7 이하)
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            String name = iterator.next();
            System.out.println(name);
        }
 
        System.out.println("********");
 
        // for문 이용
        for (String name : list) {
            System.out.println(name);
        }
 
        System.out.println("********");
 
        // Stream 은 컬렉션(배열포함)의 요소를 하나씩 참조해서 람다식으로 처리하는 반복자
        // Stream 이용(Java 8)
        Stream<String> stream = list.stream();
        stream.forEach(name->System.out.println(name)); // 최종 연산
        // 스트림은 데이터 소스로부터 데이터를 읽기만 한다. (변경 하지 않음)
        // 스트림은 Iterator처럼 1회용이다.
 
    }
}
 
 

 

 

import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
public class StreamEX3 {
    public static void main(String[] args) {
        ArrayList<Student> arr = new ArrayList<>();
        arr.add(new Student("이순신"3300));
        arr.add(new Student("김재동"1200));
        arr.add(new Student("강감찬"2170));
        arr.add(new Student("소지섭"2190));
        arr.add(new Student("소유진"1200));
        arr.add(new Student("남궁성"3270));
        arr.add(new Student("신용권"3280));
 
        // HashSet(Set인터페이스를 구현한 대표적인 컬렉션 클래스)을 사용하여 중복 제거하기
        Set<Student> arr2 = new HashSet<>(arr);
        ArrayList<Student> resArr2 = new ArrayList<>(arr2);
 
        Collections.sort(resArr2,Comparator.comparing(Student::getBan) // 반별 정렬
                        .thenComparing(Comparator.comparing(Student::getName))
                       .thenComparing(Comparator.naturalOrder()));   // 기본 정렬
        //System.out.println(resArr2);
        for(Student s : resArr2){
            System.out.println(s);
        }
 
        System.out.println("*****************");
        Stream<Student> stream = arr.stream().
                sorted(Comparator.comparing(Student::getBan).
                thenComparing(Comparator.comparing(Student::getName)).
                thenComparing(Comparator.naturalOrder()));
        stream.forEach(System.out::println);
 
        System.out.println("*****************");
        Stream<Student> studentStream = Stream.of(
                new Student("이순신"3300),
                new Student("김재동"1200),
                new Student("강감찬"2170),
                new Student("소지섭"2190),
                new Student("소유진"1200),
                new Student("남궁성"3270),
                new Student("신용권"3280)
        );
 
        studentStream.sorted(Comparator.comparing(Student::getBan) // 반별 정렬
                       .thenComparing(Comparator.naturalOrder()))  // 기본 정렬
                .forEach(System.out::println);
    }
}
 
class Student implements Comparable<Student> {
    String name;
    int ban;
    int totalScore;
 
    Student(String name, int ban, int totalScore) {
        this.name =name;
        this.ban =ban;
        this.totalScore =totalScore;
    }
 
    public String toString() {
        return String.format("[%s, %d, %d]", name, ban, totalScore);
    }
 
    String getName()     { return name;}
    int getBan()         { return ban;}
    int getTotalScore()  { return totalScore;}
 
    // 총점 내림차순을 기본 정렬로 한다.
    public int compareTo(Student s) {
        return s.totalScore - this.totalScore;
    }
}

 

Stream map 예제

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
public class StreamEX4 {
    public static void main(String[] args) {
        File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"),
                new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")
        };
 
        Stream<File> fileStream = Stream.of(fileArr);
 
        // map()으로 Stream<File>을 Stream<String>으로 변환
        Stream<String> filenameStream = fileStream.map(File::getName);
        filenameStream.forEach(System.out::println); // 모든 파일의 이름을 출력
 
        System.out.println("-----------------------");
        fileStream = Stream.of(fileArr);  // 스트림을 다시 생성
 
        // peek : 스트림의 요소를 소비하지 않고 엿보기
        // map : 기존의 Stream 요소들을 변환하여 새로운 Stream을 형성하는 연산이다.
        // distinct는 중복된 데이터를 검사하기 위해 Object의 equals() 메소드 사용한다
        fileStream.map(File::getName)     // Stream<File> → Stream<String>
                .filter(s -> s.indexOf('.')!=-1)   // 확장자가 없는 것은 제외
                //.peek(s->System.out.printf("filename=%s%n",s))
                .map(s -> s.substring(s.indexOf('.')+1)) // 확장자만 추출
                //.peek(s->System.out.printf("extension=%s%n",s))
                .map(String::toUpperCase)     // 모두 대문자로 변환
                .distinct()   //  중복 제거
                .forEach(System.out::println); // 메소드참조(MethodReferences)
 
        System.out.println();
 
        // filter : 특정조건으로 스트림의 컨텐츠를 필터링하는 것
        // c로 시작하는 문자열만 필터링하도록 조건을 설정
        List<String> list = Arrays.asList("a1","a2","b1","b2","c2","c1","c3");
        Stream<String> stream1 = list.stream();
        final String filtered = stream1.filter(s -> s.startsWith("c")).
                collect(Collectors.joining(", "));
        System.out.println(filtered);
    }
}
 

 

 

'안드로이드 > Java 문법' 카테고리의 다른 글

[java] Wrapper Class  (0) 2021.11.18
[Java] Stream 예제 2  (0) 2021.11.16
[Java] Generics 예제1  (2) 2021.11.10
HashMap 예제  (0) 2021.10.30
[Java] HashSet 를 이용한 ArrayList 중복제거 및 정렬  (0) 2020.12.12
블로그 이미지

Link2Me

,
728x90

Generics(지네릭)은 컴파일 시 타입을 체크해주는 기능으로 JDK 1.5부터 적용되었다.

실행시 발생하는 에러를 컴파일 시 체크하여 에러 발생을 사전에 방지할 수 있다.

 

장점

1. 타입 안정성을 제공한다.

2. 타입체크와 형변환을 생략할 수 있어 코드가 간결해진다.

 

Generics 개념을 이해하는 예제이다.

import java.util.ArrayList;
import java.util.List;
 
class Product {}
class Tv extends Product {
    // Product Class는 Tv Class의 부모 클래스
}
class Audio extends Product {
    // Product Class는 Audio Class의 부모 클래스
}
 
public class GenericsEx1 {
    public static void main(String[] args) {
        // 일반 클래스 (JDK 1.5 이전 방식)
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Tv());
        arrayList.add(new Audio());
 
        Tv t = (Tv) arrayList.get(0); // arrayList 의 첫번째 요소를 꺼낸다.
        // arrayList.get(0) 는 object 타입이므로 Tv 타입으로 형변환을 해줘야 한다.
        Audio audio = (Audio) arrayList.get(1); // arrayList 의 두번째 요소를 꺼낸다.
        // arrayList.get(1) 는 object 타입이므로 Audio 타입으로 형변환을 해줘야 한다.
 
        // 지네릭 클래스 : JDK 1.5 부터 적용
        ArrayList<Product> productList = new ArrayList<Product>();
        // 타입변수 : 참조변수의 타입 매개변수와 생성자의 타입 매개변수는 일치해야 한다.
        // 즉, 참조변수와 생성자의 대입된 타입은 일치해야 한다.
        // ArrayList : 원시타입, Product : 타입 변수 또는 타입 매개변수
        // ArrayList<Product> : 지네릭 클래스
        productList.add(new Tv());
        productList.add(new Audio());
 
        Tv t2 = (Tv) productList.get(0);
        // productList.get(0) 의 타입이 Product 이므로 불일치하여 형변환 해줘야 한다.
        Audio audio2 = (Audio) productList.get(1);
        // productList.get(1) 의 타입이 Product 이므로 불일치하여 형변환 해줘야 한다.
 
        printAll(productList);
 
        // 지네릭 클래스
        // JDK 1.7 부터 생성자에 타입변수 생략 가능
        ArrayList<Tv> tvList = new ArrayList<>();
        tvList.add(new Tv()); // Tv 타입의 객체만 저장 가능
        tvList.add(new Tv());
        //tvList.add(new Audio()); // Audio 는 TV의 자손이 아니므로 에러 발생
 
        //printAll(tvList); // 컴파일 에러가 발생한다.
        // new ArrayList<Tv>() 객체 타입 Tv과 printAll 메소드의 매개변수 타입 Product 불일치로 에러 발생
 
        // List 와 ArrayList 의 관계는 조상과 자손의 관계이며,
        // 조상의 참조변수로 자손의 객체 생성하는 것은 가능하다.(다형성)
        List<Tv> tvList1 = new ArrayList<Tv>(); // OK. 다형성
        tvList1.add(new Tv());
        tvList1.add(new Tv());
    }
 
    private static void printAll(ArrayList<Product> list) {
        for (Product p : list)
            System.out.println(p);
    }
}
 

 

 

 

블로그 이미지

Link2Me

,
728x90

HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션이다.

key와 value의 쌍으로 이루어진 데이터를 저장하는 구조로 이루어져 있다.

값은 중복 저장될 수 있지만 키는 중복 저장될 수 없다. 기존에 저장된 키와 동일한 키로 값을 저장하면 새로운 값으로 대치된다.

 

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
 
public class HashMapEx {
    public static void main(String[] args) {
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"강아지"); //값 추가
        map.put(2,"고양이");
        map.put(3,"오소리");
        map.put(3,"호랑이"); // 값만 업데이트 된다. (키는 중복 없음)
 
        System.out.println("All key-value pairs");
        for(Integer key : map.keySet()) {
            System.out.println("{"+key+","+map.get(key)+"}");
        }
 
        // Foreach() 메소드로 순환하기
        map.forEach((key,value)-> {
            System.out.println("["+key+","+value+"]");
        });
 
        Iterator<Map.Entry<Integer, String>> entries = map.entrySet().iterator();
        while(entries.hasNext()){
            Map.Entry<Integer, String> entry = entries.next();
            System.out.println("[Key]:" + entry.getKey() + " [Value]:" +  entry.getValue());
        }
 
        Iterator<Integer> keys = map.keySet().iterator();
        // 특정 key값의 value를 가져오고싶다면 get(key)를 사용
        while(keys.hasNext()){
            int key = keys.next();
            System.out.println("[Key]:" + key + " [Value]:" +  map.get(key));
        }
 
        if(map.containsKey(2)){
            System.out.println" Selected Value :" +  map.get(2));
        }
    }
}
 

 

 

블로그 이미지

Link2Me

,
728x90

viewModel 기반에서 RecyclerView 처리하는 예제를 작성해보고 있는데 생각만큼 쉽지 않다.

viewBinding 은 기본으로 사용해야 하는 것 같아 viewBinding 처리했고 viewModel 부분은 많은 연습을 해야 사용법이 익숙해질 듯 하다.

 

 
import com.link2me.android.recyclerviewmodel.adapter.ContactsListAdapter
import com.link2me.android.recyclerviewmodel.databinding.ActivityMainBinding
import com.link2me.android.recyclerviewmodel.model.ContactData
import com.link2me.android.recyclerviewmodel.model.ContactDataDto
import com.link2me.android.recyclerviewmodel.retrofit.RetrofitService
import com.link2me.android.recyclerviewmodel.viewmodel.MainViewModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
 
class MainActivity : AppCompatActivity() {
    private val TAG = this.javaClass.simpleName
 
    lateinit var mContext: Context
    private lateinit var mAdapter: ContactsListAdapter // 리스트뷰에 사용되는 ListViewAdapter
 
    private val addressItemList = mutableListOf<ContactData>() // 서버에서 가져온 원본 데이터 리스트
    private val searchItemList = mutableListOf<ContactData>() // 서버에서 가져온 원본 데이터 리스트
 
    private lateinit var backPressHandler: BackPressHandler
 
    val binding by lazy { ActivityMainBinding.inflate(layoutInflater)  }
 
    lateinit var viewModel: MainViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        //setContentView(R.layout.activity_main)
        mContext = this@MainActivity
 
        Log.e(TAG, "onCreate")
 
        backPressHandler = BackPressHandler(this// 뒤로 가기 버튼 이벤트
 
        // 뷰모델 가져오기
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
 
        // 관찰하여 데이터 값이 변경되면 호출
        viewModel.getContactsData().observe(this, listUpdateObserver)
    }
 
    var listUpdateObserver: Observer<List<ContactData>> =
        Observer {
            mAdapter = ContactsListAdapter(mContext) // Adapter 생성
            binding.myRecyclerView.adapter = mAdapter // 어댑터를 리스트뷰에 세팅
            binding.myRecyclerView.layoutManager = LinearLayoutManager(this)
            mAdapter.submitList(it)
 
            addressItemList.clear()
            addressItemList.addAll(it)
 
            binding.search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
                override fun onQueryTextSubmit(query: String): Boolean {
                    // 문자열 입력을 완료했을 때 문자열 반환
                    return false
                }
 
                override fun onQueryTextChange(newText: String): Boolean {
                    // 문자열이 변할 때마다 바로바로 문자열 반환
                    filter(newText)
                    return false
                }
            })
 
            mAdapter.setItemClickListener(object : ContactsListAdapter.OnItemClickListener {
                override fun onClick(v: View, position: Int) {
                    val contacts = addressItemList[position]
                    Toast.makeText(mContext, "클릭한 이름은 ${contacts.userNM} 입니다.", Toast.LENGTH_SHORT)
                        .show()
                }
            })
 
        }
 
    fun filter(charText: String) {
        // ListAdapter 에서 처리하는 방법을 알게되면 수정할 예정
        var charText = charText
        charText = charText.toLowerCase(Locale.getDefault())
        searchItemList.clear()
        if (charText.length == 0) {
            mAdapter.submitList(addressItemList)
        } else {
            for (wp in addressItemList) {
                if (Utils.isNumber(charText)) { // 숫자여부 체크
                    if (wp.mobileNO.contains(charText) || wp.officeNO.contains(charText)) {
                        // 휴대폰번호 또는 사무실번호에 숫자가 포함되어 있으면
                        searchItemList.add(wp)
                        mAdapter.submitList(searchItemList)
                    }
                } else {
                    val iniName = HangulUtils.getHangulInitialSound(wp.userNM, charText)
                    if (iniName!!.indexOf(charText) >= 0) { // 초성검색어가 있으면 해당 데이터 리스트에 추가
                        searchItemList.add(wp)
                        mAdapter.submitList(searchItemList)
                    } else if (wp.userNM.toLowerCase(Locale.getDefault()).contains(charText)) {
                        searchItemList.add(wp)
                        mAdapter.submitList(searchItemList)
                    }
                }
            }
        }
        mAdapter.notifyDataSetChanged()  // 이거 없애면 어플 비정상 종료 처리됨
    }
 
    override fun onBackPressed() {
        backPressHandler.onBackPressed()
    }
}

단순하게 데이터를 서버에서 받아서 RecyclerView 에 출력하는 것은 어렵지 않다.

검색 기능을 추가하려니까 기존 사용하던 방식의 RecyclerView 와 약간 다른 부분을 처리하기 위해 ArrayList 를 여러개 생성해서 하는 것이 비효율적인 듯 싶다.

 

package com.link2me.android.recyclerviewmodel.viewmodel
 
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.link2me.android.recyclerviewmodel.model.ContactData
import com.link2me.android.recyclerviewmodel.model.ContactDataDto
import com.link2me.android.recyclerviewmodel.retrofit.RetrofitService
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
 
// 데이터의 변경사항을 알려주는 라이브 데이터를 가지는 뷰모델
class MainViewModel : ViewModel() {
    private val TAG = this.javaClass.simpleName
 
    // 뮤터블 라이브 데이터 - 수정 가능, 라이브 데이터 - 값 변경 안됨
    private val liveData: MutableLiveData<List<ContactData>> by lazy {
        MutableLiveData<List<ContactData>>().also {
            loadContacts()
        }
    }
 
    fun getContactsData(): LiveData<List<ContactData>> {
        return liveData
    }
 
    // LiveData는 관찰 가능한 데이터들의 홀더 클래스
 
    private fun loadContacts(){
        RetrofitService.getInstance()?.getContactDataResult("1")?.enqueue(object :
            Callback<ContactDataDto> {
            override fun onResponse(call: Call<ContactDataDto>, response: Response<ContactDataDto>) {
                response.body()?.let {
                    if(it.status.contains("success")){
                        liveData.value = it.message
                    }
                }
            }
 
            override fun onFailure(call: Call<ContactDataDto>, t: Throwable) {
                Log.d(TAG,"Retrofit response fail.")
                t.stackTrace
            }
 
        })
    }
 
    fun searchNameChanged(name: String) {
 
    }
 
}
 
 

 

 

Adapter 코드는 포멧만 한번 만들어두면 몇가지만 코드만 수정하면 되기 때문에 재활용하는 것은 쉬운 편이다.

package com.link2me.android.recyclerviewmodel.adapter
 
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.telephony.PhoneNumberUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.link2me.android.recyclerviewmodel.databinding.ItemAddressBinding
import com.link2me.android.recyclerviewmodel.model.ContactData
import com.link2me.android.recyclerviewmodel.retrofit.RetrofitURL
import java.util.*
 
class ContactsListAdapter(val context: Context) : ListAdapter<ContactData, ContactsListAdapter.ViewHolder>(diffUtil) {
 
    // itemClickListener 를 위한 인터페이스 정의
    interface OnItemClickListener {
        fun onClick(v: View, position: Int)
    }
    private lateinit var itemClickListener: OnItemClickListener
 
    fun  setItemClickListener(itemClickListener: OnItemClickListener){
        this.itemClickListener = itemClickListener
    }
    //////////////////////////////////////////////////////////////////
 
    // val 예약어로 바인딩을 전달 받아서 전역으로 사용힌다. 그리고 상속받는 ViewHolder 생성자에는 꼭 binding.root를 전달해야 한다.
    inner class ViewHolder(private val binding: ItemAddressBinding) : RecyclerView.ViewHolder(binding.root){
        // View와 데이터를 연결시키는 함수
        fun bind(item: ContactData){
            //binding.executePendingBindings() //데이터가 수정되면 즉각 바인딩
 
            if (item.photo.contains("jpg")){
                val photoURL = RetrofitURL.Photo_URL + item.photo
                Glide.with(context).load(photoURL).into(binding.profileImage)
            }
 
            // 생성자에서 val로 받았기 때문에 홀더 내부 어디에서나 binding 사용가능
            binding.itemName.text = item.userNM
            binding.itemMobileNO.text = item.mobileNO
 
            val items = arrayOf("휴대폰 전화걸기""연락처 저장")
            val builder = AlertDialog.Builder(context)
            builder.setTitle("해당작업을 선택하세요")
            builder.setItems(items, DialogInterface.OnClickListener { dialog, which ->
                Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show()
                when (which) {
                    0 -> {
                        if (item.mobileNO.length == 0) {
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.", Toast.LENGTH_SHORT).show()
                            return@OnClickListener
                        }
                        val dialog1 = AlertDialog.Builder(context)
                            .setTitle(item.userNM)
                            .setMessage(PhoneNumberUtils.formatNumber(item.mobileNO) + " 통화하시겠습니까?")
                            .setPositiveButton("예",
                                { dialog, which ->
                                    val intent = Intent(Intent.ACTION_CALL,
                                        Uri.parse("tel:" + PhoneNumberUtils.formatNumber(item.mobileNO))
                                    )
                                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                                    context.startActivity(intent)
                                })
                            .setNegativeButton("아니오") { dialog, which -> dialog.dismiss() }.create()
                        dialog1.show()
                    }
                    1 -> Toast.makeText(context, "전화번호 저장하는 로직은 직접 구현하시기 바랍니다.", Toast.LENGTH_SHORT).show()
                }
            })
            builder.create()
            binding.itemBtn.setOnClickListener { builder.show() }
        }
    }
 
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        //val rootView = LayoutInflater.from(parent.context).inflate(R.layout.item_address, parent,false)
        return ViewHolder(ItemAddressBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }
 
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        // RecyclerView는 ViewHolder를 데이터와 연결할 때 이 메서드를 호출한다.
 
        holder.itemView.setOnClickListener {
            itemClickListener.onClick(it,position)
        }
 
        holder.apply {
            bind(currentList[position])
        }
    }
 
    companion object {
        val diffUtil = object : DiffUtil.ItemCallback<ContactData>() {
            override fun areContentsTheSame(oldItem: ContactData, newItem: ContactData) =
                oldItem == newItem
 
            override fun areItemsTheSame(oldItem: ContactData, newItem: ContactData) =
                oldItem.idx == newItem.idx
        }
    }
 
}
 

 

전체 소스코드는 https://github.com/jsk005/KotlinProjects/tree/master/recyclerviewmodel_1 에 올려두었다.

관련 기능을 하나 하나 테스트하면서 익히고 기록해 두려고 한다.

 

 

 

블로그 이미지

Link2Me

,
728x90

https://material.io/resources/color/ 사이트에 접속한다.

 

 

위와 같은 기본 UI 구성을 할 수 있도록 구성되어 있다.

먼저 Primary 를 선택하고 색상표에서 원하는 색상을 선택한다.

그 다음에 Secondary를 선택하고 원하는 색상을 선택한다.

Text on P 도 마찬가지로 색상을 선택하거나 Custom에서 선택한다.

 

 

내가 예시로 선택한 화면이다.

 

이제 Export를 눌러서 Android를 선택하여 저장하면 color.xml 파일로 저장된다.

 

<!--?xml version="1.0" encoding="UTF-8"?-->
<resources>
  <color name="primaryColor">#2196f3</color>
  <color name="primaryLightColor">#6ec6ff</color>
  <color name="primaryDarkColor">#0069c0</color>
  <color name="secondaryColor">#ededed</color>
  <color name="secondaryLightColor">#ffffff</color>
  <color name="secondaryDarkColor">#bbbbbb</color>
  <color name="primaryTextColor">#ffffff</color>
  <color name="secondaryTextColor">#000000</color>
</resources>

 

Material Design color palettes

https://material.io/design/color/the-color-system.html#tools-for-picking-colors 사이트에 색상표가 잘 표현되어 있다.

 

스타일 및 테마

https://developer.android.com/guide/topics/ui/look-and-feel/themes?hl=ko 

 

스타일 및 테마  |  Android 개발자  |  Android Developers

스타일 및 테마 Android에서 스타일 및 테마를 사용하면 웹 디자인의 스타일시트와 유사하게 앱 디자인의 세부사항을 UI 구조 및 동작과 분리할 수 있습니다. 스타일은 단일 View의 모양을 지정하는

developer.android.com

 

Theme 와 Dark Theme 를 고려하여 List Item 의 구분선을 표시하고자 할 때

<solid android:color="?attr/backgroundColor"/> 와 같이 배경색 지정을 Theme 와 Dark Theme 각각 다르게 인식할 수 있도록 ?attr/backgroundColor 로 처리하면 된다.

 

res/drawable/bg_border.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:aapt="http://schemas.android.com/aapt">
    <item>
        <shape android:shape="rectangle">
            <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="1dp"/>
            <stroke android:width="1dp" android:color="#ebebeb"/>
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <solid android:color="?attr/backgroundColor"/>
        </shape>
    </item>
</layer-list>
 

 

디자인 감각 부족으로 테마 색상 표현력이 부족 하지만, backgroundColor 는 흰색과 검은색으로 테마에 따라 구분되어지는 걸 확인할 수 있다.

어두운 테마(Dark Theme)는 Android 10 (API 레벨 29) 이상에서 제공된다.

MaterialComponents의 어두운 테마 설정을 사용할 수도 있다.
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/blue_700</item>
        <item name="colorPrimaryVariant">@color/blue_200</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">#ff625b71</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
        <item name="backgroundColor">@color/white</item>
    </style>
</resources>
 
<!-- Dark Theme -->
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/blue_500</item>
        <item name="colorPrimaryVariant">@color/blue_200</item>
        <item name="colorOnPrimary">@color/black</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">#ffccc2dc</item>
        <item name="colorSecondaryVariant">@color/teal_200</item>
        <item name="colorOnSecondary">@color/white</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
        <item name="backgroundColor">@color/black</item>
    </style>
</resources>

 

http://android-doc.github.io/training/material/theme.html 에 각 영역의 색상 폰 이미지를 참조하면 된다.

'안드로이드 > Layout' 카테고리의 다른 글

CoordinatorLayout custom toolbar  (0) 2021.08.29
Material Components color  (0) 2021.08.19
Shape Drawable로 간단한 배경 이미지 활용  (0) 2021.08.12
CardView tools:visibility  (0) 2021.08.12
android 검색 + 버튼 배치  (0) 2021.03.14
블로그 이미지

Link2Me

,
728x90

CoordinatorLayout 은 기본 API가 아닌 Support Design Widget 이다.

 

앱 build.gradle 추가

dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation 'com.google.android.material:material:1.1.0'
}

 

Layout 구성 형태

 

AppBarLayout
안드로이드에는 원래 ActionBar가 기본적으로 제공되고 있었지만, 액션바는 안드로이드 API 버전마다 다른 기능과 다른 동작을 제공해 개발자가 사용자에게 일관된 UX를 제공하는 데 불편함이 있었다.
이에 대한 해결책으로 google은 material design 패키지에 있는 AppBarLayout과 ToolBar를 제시하고 있다.

 

activity_customer.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    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"
    tools:context=".ui.CustomerActivity">
 
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.Base.AppBarOverlay">
 
        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/AppTheme.Base.PopupOverlay" />
    </com.google.android.material.appbar.AppBarLayout>
 
    <include layout="@layout/content_customer_detail"/>
 
</androidx.coordinatorlayout.widget.CoordinatorLayout>
 

content_customer_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="@dimen/screen_edge_margin"
    android:background="@color/colorWhite"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".ui.CustomerActivity"
    tools:showIn="@layout/activity_customer" >
 
    <WebView
        android:id="@+id/custHomepageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
 
</LinearLayout>

 

툴바 Home 버튼을 누르면 현재 Activity 를 스택에서 제거하는 코드가 아래 포함되어 있다.

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
 
class CustomerActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_customer)
        context = this@CustomerActivity
 
        setSupportActionBar(toolbar)
        supportActionBar?.let {
            it.setDisplayHomeAsUpEnabled(true)
            it.setDisplayShowHomeEnabled(true)
        }
 
    }
 
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.getItemId()) {
            android.R.id.home -> {
                finish() // toolbar HOME 버튼 누르면 현재 Activity 스택에서 제거
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }
 
    override fun onBackPressed() {
        finish()
    }
}
 

 

기본 툴바가 아니라 커스텀 툴바로 버튼 아이콘을 추가하고 싶을 때가 있다.

이럴 경우 아래와 같이 수정하면 된다.

 

res/menu/menu_edit.xml

<?xml version="1.0" encoding="utf-8"?>
<menu 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"
    tools:context=".ui.CustomerActivity">
    <item
        android:id="@+id/edit"
        android:icon="@drawable/ic_edit_white_24dp"
        android:title="편집"
        app:showAsAction="always" />
</menu>

 

drawable/ic_edit_white_24dp.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
  <path
      android:pathData="M14.06,9.02l0.92,0.92L5.92,19L5,19v-0.92l9.06,-9.06M17.66,3c-0.25,0 -0.51,0.1 -0.7,0.29l-1.83,1.83 3.75,3.75 1.83,-1.83c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.2,-0.2 -0.45,-0.29 -0.71,-0.29zM14.06,6.19L3,17.25L3,21h3.75L17.81,9.94l-3.75,-3.75z"
      android:fillColor="#ffffff"/>
</vector>

 

 

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
 
class CustomerActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_customer)
        context = this@CustomerActivity
 
        setSupportActionBar(toolbar)
        supportActionBar?.let {
            it.setDisplayHomeAsUpEnabled(true)
            it.setDisplayShowHomeEnabled(true)
        }
 
    }
 
    // 커스텀 메뉴 추가 목적 (menu 폴더에 있는 xml 파일)
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_edit, menu)
        return true
    }
 
    override fun onOptionsItemSelected(item: MenuItem): Boolean = when(item.itemId) {
        android.R.id.home -> {
            finish() // toolbar HOME 버튼 누르면 현재 Activity 스택에서 제거
            true
        }
        R.id.edit -> {
            finish()
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
 
    override fun onBackPressed() {
        finish()
    }
}
 

 

'안드로이드 > Layout' 카테고리의 다른 글

Material Design Color TOOL 사용법  (0) 2021.09.10
Material Components color  (0) 2021.08.19
Shape Drawable로 간단한 배경 이미지 활용  (0) 2021.08.12
CardView tools:visibility  (0) 2021.08.12
android 검색 + 버튼 배치  (0) 2021.03.14
블로그 이미지

Link2Me

,
728x90

Material Components color 색상 예시

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">@color/green_700</color>
    <color name="colorPrimaryVariant">@color/blue_700</color>
    <color name="colorSecondary">@color/green_200</color>
    <color name="colorSecondaryVariant">@color/green_500</color>
 
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
 
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="deep_purple_200">#B39DDB</color>
    <color name="deep_purple_500">#673AB7</color>
 
    <color name="blue_200">#90CAF9</color>
    <color name="blue_500">#2196f3</color>
    <color name="blue_600">#1e88e5</color>
    <color name="blue_700">#1976d2</color>
    <color name="light_blue_200">#81D4FA</color>
    <color name="light_blue_500">#03A9F4</color>
 
    <color name="green_200">#03dac6</color>
    <color name="green_500">#018786</color>
    <color name="green_700">#00A99D</color>
    <color name="light_green_200">#C5E1A5</color>
    <color name="light_green_500">#8BC34A</color>
 
    <color name="red_200">#cf6679</color> <!--Color error (dark)-->
    <color name="red_500">#f44336</color>
    <color name="red_600">#b00020</color> <!--Color error (light)-->
 
    <color name="pink_200">#F48FB1</color>
    <color name="pink_500">#E91E63</color>
 
    <color name="indigo_200">#9FA8DA</color>
    <color name="indigo_500">#3F51B5</color>
 
    <color name="cyan_200">#80DEEA</color>
    <color name="cyan_500">#00BCD4</color>
 
    <color name="lime_200">#E6EE9C</color>
    <color name="lime_500">#CDDC39</color>
 
    <color name="yellow_200">#FFF59D</color>
    <color name="yellow_500">#FFEB3B</color>
 
    <color name="amber_200">#FFE082</color>
    <color name="amber_500">#FFC107</color>
 
    <color name="orange_200">#FFCC80</color>
    <color name="orange_500">#FF9800</color>
    <color name="deep_orange_50">#fbe9e7</color>
    <color name="deep_orange_200">#FFAB91</color>
    <color name="deep_orange_300">#ff8a65</color>
    <color name="deep_orange_500">#FF5722</color>
    <color name="deep_orange_700">#e64a19</color>
 
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_500">#009688</color>
    <color name="teal_700">#FF018786</color>
 
    <color name="black">#FF000000</color>
    <color name="black_800">#121212</color> 
 
    <color name="white">#FFFFFFFF</color>
    <color name="background">#eee</color>
 
    <color name="transparent">#00000000</color><!-- 투명 -->
    <color name="transparentBlack">#96000000</color>
 
    <color name="colorBlack">#ff000000</color>
    <color name="colorIvory">#fffff0</color>
    <color name="colorWhite">#ffffffff</color>
    <color name="colorWhiteGray">#f0f0f0</color>
    <color name="colorGray">#ff8a8a8a</color>
    <color name="colorDarkGray">#4c4c4e</color>
    <color name="colorLightGray">#ffeaeaea</color>
    <color name="colorMint">#ff00c0aa</color>
    <color name="colorLightMint">#1000c0aa</color>
    <color name="colorRed">#ffD81B60</color>
    <color name="colorLightRed">#ffff4444</color>
    <color name="colorOrangeDark">#ffff8800</color>
    <color name="colorYellow">#ffFFEC19</color>
    <color name="colorBlue">#0385ff</color>
    <color name="colorSkyBlue">#87ceeb</color>
    <color name="colorYellowGreen">#9acd32</color>
    <color name="colorGreenYellow">#adff2f</color>
    <color name="colorGreen">#00ff00</color>
 
    <color name="colorTitle">#de000000</color>
    <color name="colorDetail">#8a000000</color>
 
    <color name="colorDivider">#1f000000</color>
 
</resources>

 

블로그 이미지

Link2Me

,
728x90

https://developer.android.com/guide/topics/resources/drawable-resource?hl=ko#Shape 

 

드로어블 리소스  |  Android 개발자  |  Android Developers

드로어블 리소스는 화면에 그릴 수 있으며 getDrawable(int)와 같은 API를 사용하여 가져오거나 android:drawable 및 android:icon과 같은 특성을 사용하여 다른 XML 리소스에 적용할 수 있는 그래픽에 대한 일

developer.android.com

를 참고하면 된다.

 

실제로 비트맵을 사용하지  않아도 되므로 APK 파일 용량도 줄어들고 색상 변경이 편리하다.

 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#FF7272" />
    <size
        android:width="44dp"
        android:height="44dp" />
</shape>

oval : 타원형

rectangle : 사각형(default)

 

Layout 파일에서 사용법

<LinearLayout
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="30dp"
    android:gravity="center"
    app:layout_constraintTop_toBottomOf="@id/addButton"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent">
 
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:background="@drawable/circle_red"
        android:gravity="center"
        android:textColor="@color/white"
        android:textSize="18sp"
        android:textStyle="bold"
        android:visibility="gone"
        tools:visibility="visible"
        tools:text="1" />
 
</LinearLayout>

shape_drawable.zip
0.00MB

 

kotlin 코드

private fun setNumberBackground(number:Int, textView: TextView) {
    when(number) {
        in 1..10 -> textView.background = ContextCompat.getDrawable(this, R.drawable.circle_yello)
        in 11..20 -> textView.background = ContextCompat.getDrawable(this, R.drawable.circle_blue)
        in 21..30 -> textView.background = ContextCompat.getDrawable(this, R.drawable.circle_red)
        in 31..40 -> textView.background = ContextCompat.getDrawable(this, R.drawable.circle_gray)
        else -> textView.background = ContextCompat.getDrawable(this, R.drawable.circle_green)
 
    }
}

'안드로이드 > Layout' 카테고리의 다른 글

CoordinatorLayout custom toolbar  (0) 2021.08.29
Material Components color  (0) 2021.08.19
CardView tools:visibility  (0) 2021.08.12
android 검색 + 버튼 배치  (0) 2021.03.14
하단 TabLayout + ViewPager2 Layout 예제  (0) 2020.12.30
블로그 이미지

Link2Me

,
728x90

CardView를 쓰면 입체감을 줄 수 있는데, 단 하나의 레이아웃만을 가질 수 있다.
아이템 전체를 CardView 로 만들려면 <LinearLayout>...</LinearLayout> 으로 모든 내용을 감싸야 한다.

 

Android Studio 에서 Layout 의 default 는 gone 으로 처리하고, 디자인하는 화면에서는 보이게 하고 싶을 때가 있다.

android:visibility="gone"

tools:visibility="visible"

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/view_business_card"
    android:layout_width="match_parent"
    android:visibility="gone"
    tools:visibility="visible"
    android:layout_height="wrap_content"
    android:background="#ffffff"
    android:clickable="true"
    app:cardElevation="5dp"
    app:cardUseCompatPadding="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
 
    </LinearLayout>
 
</androidx.cardview.widget.CardView>
 

 

블로그 이미지

Link2Me

,
728x90

Android 에서 파일에 LogData를 기록할 목적으로 만든 코드이다.

 

public static void dataSaveLog(String _log, String _fileName) {
    /* SD CARD 하위에 LOG 폴더를 생성하기 위해 미리 dirPath에 생성 할 폴더명을 추가 */
    String dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/LOG/";
    File file = new File(dirPath);
 
    // 일치하는 폴더가 없으면 생성
    if (!file.exists())
        file.mkdirs();
 
    // txt 파일 생성
    File savefile = new File(dirPath + "LOG_" + _fileName + ".txt");
    try {
        BufferedWriter bfw = new BufferedWriter(new FileWriter(dirPath + "LOG_" + _fileName + ".txt"true));
        bfw.write(_log);
        bfw.write("\n");
        bfw.flush();
        bfw.close();
    } catch (IOException e){
        /* Exception */
    }
}
 

 

Android 11 에서 USB 케이블 없이 Wi-Fi 로 연결하여 콘솔앱에서 출력되는 메시지를 확인할 수 있어서

지금은 위 코드를 굳이 사용할 필요는 없어졌다.

 

Android Studio 에서 ADB를 이용하여 LogCat 정보를 확인하는 방법

adb tcpip 5555

// 192.168.1.32 는 스마트폰이 연결된 Wi-Fi 주소

adb connect 192.168.1.32:5555

 

'안드로이드 > Android Serial 통신' 카테고리의 다른 글

Geateway 파싱 처리  (0) 2020.05.23
AlertDialog EditText  (0) 2019.06.26
Android 팝업창(popup window) 만들기  (0) 2019.06.01
android handler 를 이용한 지연처리  (0) 2019.05.09
Serial Communication Key  (0) 2019.05.09
블로그 이미지

Link2Me

,
728x90

오랫만에 Android Studio 를 구동시켰더니 classpath "com.android.tools.build:gradle:7.0.0" 로 업데이트되고 

JDK 11 버전으로 설치를 하라고 나온다.

https://www.oracle.com/java/technologies/javase-jdk11-downloads.html

에서 파일을 다운로드 받아서 설치한다.

 

 

 

Android Studio 에서 설정을 변경한다.

 

앱 build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}
 
android {
    compileSdk 30
 
    defaultConfig {
        applicationId "com.link2me.android.bdmap"
        minSdk 26
        targetSdk 30
        versionCode 1
        versionName "1.0"
    }
 
    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 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}

gradle:7.0.0 에 맞게 라이브러리를 변경해주지 않는 것은 동작되지 않는 것도 있더라.

구글 검색을 해서 변경된 라이브러리에 맞게 수정해주니 제대로 동작된다.

 

project gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = '1.5.20'
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.0"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
 
task clean(type: Delete) {
    delete rootProject.buildDir
}
 

 

settings.gradle

기존에는 프로젝트 그래들에 있었던 사항이 이제는 settings.gradle에 추가해야 정상 동작하더라.

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url "https://naver.jfrog.io/artifactory/maven/" }
        maven { url "https://jitpack.io" }
        maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
 
    }
}
rootProject.name = "BDMAP"
include ':app'
include ':gnbdmap'
 

 

 

블로그 이미지

Link2Me

,
728x90

증감 추이를 파악하기 위해 샘플로 작성한 LineChart 예제이다.

 

앱 build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
}
 
android {
    compileSdkVersion 30
 
    defaultConfig {
        applicationId "com.link2me.android.mpchartdemo"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
 
    }
 
    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.5.0'
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 
    //implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
    implementation 'com.github.PhilJay:MPAndroidChart:v2.2.5'
}
 

 

XML 파일

<?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:layout_height="match_parent"
    tools:context=".MainActivitySample">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        tools:ignore="MissingConstraints">
 
        <com.github.mikephil.charting.charts.LineChart
            android:id="@+id/linechart"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
 
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

MainActivity.kt 파일

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
import com.github.mikephil.charting.utils.ColorTemplate
import java.util.*
 
class MainActivity : AppCompatActivity() {
    private val TAG = this.javaClass.simpleName
    lateinit var lineChart: LineChart
    private val chartData = ArrayList<ChartData>()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        chartData.clear()
        addChartItem("1월"7.9)
        addChartItem("2월"8.2)
        addChartItem("3월"8.3)
        addChartItem("4월"8.5)
        addChartItem("5월"7.3)
 
        LineChartGraph(chartData, "강남")
    }
 
    private fun addChartItem(lableitem: String, dataitem: Double) {
        val item = ChartData()
        item.lableData = lableitem
        item.valData = dataitem
        chartData.add(item)
    }
 
    private fun LineChartGraph(chartItem: ArrayList<ChartData>, displayname: String) {
        lineChart = findViewById(R.id.linechart)
 
        val entries = ArrayList<Entry>()
        for (i in chartItem.indices) {
            entries.add(Entry(chartItem[i].valData.toFloat(), i))
        }
 
        val depenses = LineDataSet(entries, displayname)
        depenses.axisDependency = YAxis.AxisDependency.LEFT
        depenses.valueTextSize = 12f // 값 폰트 지정하여 크게 보이게 하기
        depenses.setColors(ColorTemplate.COLORFUL_COLORS) //
        //depenses.setDrawCubic(true); //선 둥글게 만들기
        depenses.setDrawFilled(false//그래프 밑부분 색칠
 
        val labels = ArrayList<String>()
        for (i in chartItem.indices) {
            labels.add(chartItem[i].lableData)
        }
 
        val dataSets = ArrayList<ILineDataSet>()
        dataSets.add(depenses as ILineDataSet)
        val data = LineData(labels, dataSets) // 라이브러리 v3.x 사용하면 에러 발생함
 
        lineChart.data = data
        //lineChart.animateXY(1000,1000);
        lineChart.invalidate()
    }
}

 

data class ChartData(
    var lableData: String = "",
    var valData: Double = 0.0
)

 

 

블로그 이미지

Link2Me

,
728x90

List 인터페이스는 특정한 순서가 있는 선형 자료구조 를 구현할 때 필요한 함수들을 정의하고 있다. 
ArrayList는 선형 자료구조이고, index로 각 원소 접근이 가능하다.
언제든지 새로운 원소를 추가하고, 변경하고, 삭제할 수 있다.

fun main(agrgs: Array<String>){
 
    val lableList = mutableListOf<String>()
    lableList.add("1월")
    lableList.add("2월")
    lableList.add("3월")
    lableList.add("4월")
    lableList.add("5월")
 
    for (item in lableList) // element
        println(item)
 
    for(i in 0..lableList.size-1// index
        println(lableList[i])
 
}

 

Custom ArrayList 데이터를 추가하고 출력하는 예제

데이터 추가할 때 주의하지 않으면 원하지 않는 결과가 나온다.

data class ChartData(
    var lableData: String = "",
    var lineData: Double = 0.0
)
 
fun main(agrgs: Array<String>){
    val chartData = mutableListOf<ChartData>()
    chartData.clear()
 
    // 데이터 추가
    var item = ChartData()
    item.lableData = "1월"
    item.lineData = 7.9
    chartData.add(item)
 
    item = ChartData()
    item.lableData = "2월"
    item.lineData = 8.2
    chartData.add(item)
 
    item = ChartData()
    item.lableData = "3월"
    item.lineData = 8.2
    chartData.add(item)
 
    item = ChartData()
    item.lableData = "4월"
    item.lineData = 8.5
    chartData.add(item)
 
    item = ChartData()
    item.lableData = "5월"
    item.lineData = 7.3
    chartData.add(item)
 
    // 데이터 출력 (index)
    for (i in 0..chartData.size-1)
        println("${chartData[i].lableData} , ${chartData[i].lineData} ")
 
    // 데이터 출력(element)
    for (item in chartData)
        println("${item.lableData}, ${item.lineData}")
 
}
 

 

데이터 추가 함수화한 예제

 
val chartData = mutableListOf<ChartData>()
 
data class ChartData(
    var lableData: String = "",
    var lineData: Double = 0.0
)
 
private fun addChartItem(lableitem: String, dataitem: Double) {
    val item = ChartData()
    item.lableData = lableitem
    item.lineData = dataitem
    chartData.add(item)
}
 
fun main(agrgs: Array<String>){
 
    chartData.clear()
 
    // 데이터 추가
    chartData.clear()
    addChartItem("1월"7.9)
    addChartItem("2월"8.2)
    addChartItem("3월"8.3)
    addChartItem("4월"8.5)
    addChartItem("5월"7.3)
 
    // 데이터 출력 (index)
    for (i in 0..chartData.size-1)
        println("${chartData[i].lableData} , ${chartData[i].lineData} ")
 
    // 데이터 출력(element)
    for (item in chartData)
        println("${item.lableData.replace(("[^\\d.]").toRegex(), "")}, ${item.lineData}")
        // 숫자만 추출하는 정규표현식 적용 : str.replace(("[^\\d.]").toRegex(), "")
 
}

 

 

블로그 이미지

Link2Me

,
728x90

GitHUB 에 공개된 샘플 예제는 Java 버전으로 되어 있다.

코틀린 버전 예제로 변경 연습한 코드를 적어둔다.

 

앱 build.gradle

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
}
 
android {
    compileSdkVersion 30
 
    defaultConfig {
        applicationId "com.kt.android.mpchart"
        minSdkVersion 26
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
 
    }
 
    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.5.0'
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}

 

 

 

activity_mail.xml

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        tools:ignore="MissingConstraints">
 
        <com.github.mikephil.charting.charts.LineChart
            android:id="@+id/linechart"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
 
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

코틀린 코드

MainActivity.kt

서버에서 데이터를 가져왔다고 가정하고 직접 입력된 값을 표기했다.

 

import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
import java.util.*
 
class MainActivity : AppCompatActivity() {
    private val TAG = this.javaClass.simpleName
    lateinit var lineChart: LineChart
    private val chartData = ArrayList<ChartData>()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        // 서버에서 데이터 가져오기 (서버에서 가져온 데이터로 가정하고 직접 추가)
        chartData.clear()
        addChartItem("1월"7.9)
        addChartItem("2월"8.2)
        addChartItem("3월"8.3)
        addChartItem("4월"8.5)
        addChartItem("5월"7.3)
 
        // 그래프 그릴 자료 넘기기
        LineChart(chartData)
    }
 
    private fun addChartItem(lableitem: String, dataitem: Double) {
        val item = ChartData()
        item.lableData = lableitem
        item.lineData = dataitem
        chartData.add(item)
    }
 
    private fun LineChart(chartData: ArrayList<ChartData>) {
        lineChart = findViewById(R.id.linechart)
 
        val entries = mutableListOf<Entry>()  //차트 데이터 셋에 담겨질 데이터
 
        for (item in chartData) {
            entries.add(Entry(item.lableData.replace(("[^\\d.]").toRegex(), "").toFloat(), item.lineData.toFloat()))
        }
 
        //LineDataSet 선언
        val lineDataSet: LineDataSet
        lineDataSet = LineDataSet(entries, "라인챠트 예시")
        lineDataSet.color = Color.BLUE  //LineChart에서 Line Color 설정
        lineDataSet.setCircleColor(Color.DKGRAY)  // LineChart에서 Line Circle Color 설정
        lineDataSet.setCircleHoleColor(Color.DKGRAY) // LineChart에서 Line Hole Circle Color 설정
 
        val dataSets = ArrayList<ILineDataSet>()
        dataSets.add(lineDataSet) // add the data sets
 
        // create a data object with the data sets
        val data = LineData(dataSets)
 
        // set data
        lineChart.setData(data)
        lineChart.setDescription(null); //차트에서 Description 설정 삭제
 
    }
 
}
 

 

 

블로그 이미지

Link2Me

,
728x90

https://developer.android.com/topic/libraries/architecture/viewmodel 에 잘 설명되어 있다.

ViewModel 를 사용하는 가장 큰 이유는 UI 와 로직의 분리이다.

액티비티, 프래그먼트 생명주기에 종속되지 않게 할 수 있다는 점이 가장 큰 매력이다.

실행되는 앱을 가로모드, 세로모드로 변경하면 값이 초기화되는데 ViewModel 을 사용하면 값이 유지된다.

 

ViewModel에는 onCleared() 함수가 존재한다.

 

jetpack LiveData

Observer에게 데이터 변경에 대한 알림을 보내는 클래스이다.

계속해서 데이터를 관찰하고 업데이트되기 때문에 UI 와 데이터간에 일치성을 가진다는 장점을 가지고 있다.

 

ViewModel 에서 context나 activity객체를 사용하고 싶다면 AndroidViewModel() 사용해야 한다.

 

여러 유투브 동영상을 보고 "개발하는 정대리" https://www.youtube.com/watch?v=-b0VNKw_niY 강좌가 간단하면서도 개념 이해하는데 도움이 되는 거 같아서 이걸 보면서 연습한 코드를 적어둔다.

동영상 강좌 중에 ViewModelProvieders.of(this).get(~~) 이런식으로 설명한 코드가 있는데 테스트 해보니

ViewModelProviders는 deprecated 되었더라. 그러므로 ViewModelProvider를 사용해줘야 한다.

 

앱 build.gradle 추가사항

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
android {
    buildFeatures { // 뷰 바인딩 사용하겠다.
        viewBinding true
    }
}
 
dependencies {
 
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.5.0'
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 
    // 뷰모델 (ViewModel)
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    // 라이브 데이터(LiveData) - 옵저버 패턴 관련 - 데이터의 변경 사항을 알 수 있다.
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
}

 

MainViewModel.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
 
enum class ActionType {
    PLUS, MINUS
}
 
// 데이터의 변경 : 뷰모델은 데이터의 변경 사항을 알려주는 라이브 데이터를 가지고 있음
class MainViewModel : ViewModel() {
 
    companion object{
        const val TAG: String = "로그"
    }
 
    // 뮤터블 라이브 데이터 - 수정 가능
    // 라이브 데이터 - 값 변경 안됨
 
    // 내부에서 설정하는 자료형은 뮤터블로 변경가능하도록 설정
    private val _currentValue = MutableLiveData<Int>()
 
    // 변경되지 않는 데이터를 가져올 때 이름을 _언더스코어 없이 설정
    // 공개적으로 가져오는 변수는 private 이 아닌 public으로 외부에서도 접근 가능하도록 설정
    // 하지만 값을 직접 라이브데이터에 접근하지 않고 뷰모델을 통해 가져올 수 있도록 설정
    val currentValue: LiveData<Int>
        get() = _currentValue
 
    // 초기값 설정
    init {
        Log.d(TAG, " MainViewModel - 생성자 호출")
        _currentValue.value = 0
    }
 
    fun updateValue(actionType: ActionType, input: Int){
        when(actionType){
            ActionType.PLUS ->
                _currentValue.value = _currentValue.value?.plus(input)
            ActionType.MINUS ->
                _currentValue.value = _currentValue.value?.minus(input)
        }
 
    }
}

 

MainActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class MainActivity : AppCompatActivity(), View.OnClickListener {
 
    companion object{
        const val TAG: String = "로그"
    }
 
    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
 
    // 나중에 값이 설정될 것이라고 lateinit 으로 설정
    lateinit var mainViewModel: MainViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        setContentView(binding.root) // View Bindg 과정
 
        // 뷰 모델 프로바이더를 통해 뷰모델 가져오기
        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        // 뷰모델이 가지고 있는 값의 변경사항을 관찰할 수 있는 라이브 데이터를 관찰한다
        mainViewModel.currentValue.observe(this, Observer {
            Log.d(TAG,"MainActivity - mainViewModel - CurrentValue 라이브 데이터 값 변경 : $it")
            binding.tvNumber.text = it.toString()
        })
 
        // 리스너 연결
        binding.btnPlus.setOnClickListener(this)
        binding.btnMinus.setOnClickListener(this)
    }
 
    override fun onClick(view: View?) {
        val userInput: Int  = binding.etNumber.text.toString().toInt()
 
        // ViewModel 에 LiveData 값을 변경하는 메소드
        when(view){
            binding.btnPlus ->
                mainViewModel.updateValue(actionType = ActionType.PLUS, userInput)
            binding.btnMinus ->
                mainViewModel.updateValue(actionType = ActionType.MINUS, userInput)
        }
 
    }
}
 

 

내 GitHub 에 올린 전체 소스 코드

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

블로그 이미지

Link2Me

,
728x90

구글에서 검색어 android kotlin view bindg 을 검색하면, https://developer.android.com/topic/libraries/view-binding?hl=ko 를 검색된다.

view binding은 Android Studio 3.6 Canary 11 이상에서 사용할 수 있다.

 

앱 build.gradle 추가사항

android {
        ...
        viewBinding {
            enabled = true
        }
    }

 

또는

android {
    ...
    buildFeatures {
        viewBinding true
   }

}


    

activity_main.xml

<?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>

 

view binding 을 사용하지 않았을 때는

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) // layout 과 연결

    }

view binding 을 사용시에는 setContentView(R.layout.activity_main)  대신에 코드를 변경해야 한다.

view binding 이름을 명명하는 원칙이 있는데, activity_main.xml 에서 첫글자는 대문자로하는 카멜 표기법으로 즉, ActivityMainBinding 으로 한다. 메인 액티비티 --> 액티비티 메인 바인딩

코드는 프래그먼트에서 뷰 결합 사용시와의 일관성 유지를 위한 코드를 작성하는 것이 좋을 듯하다.

아래 샘플 코드는 https://developer.android.com/topic/libraries/view-binding?hl=ko 에 설명된 예제

private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

 

class MainActivity : AppCompatActivity() {
    // 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) // View Bindg 과정

        // 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
    }

 

 

위 코드에서 바인딩 방법을 다르게 하는 예제

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root) // View Bindg 과정

        // 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
    }

 

 

RecyclerView 에서 view binding 하는 사항은 https://stackoverflow.com/questions/60423596/how-to-use-viewbinding-in-a-recyclerview-adapter 의 답변을 참조하거나, https://github.com/jsk005/KotlinProjects/blob/master/expandablerv/src/main/java/com/link2me/expandablerv/ExpandableAdapter.kt 를 참조하면 도움될 것이다.

 

블로그 이미지

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

developers.kakao.com/ 에서 로그인을 한다.

 

 

 

build.gradle(Project)에서 카카오 레파지토리 설정

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://navercorp.bintray.com/maps' }
        maven { url "https://jitpack.io" }
        //mavenCentral()
        maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
    }
}
 

 

 

 

build.gradle (module)에 dependencies 설정

 

dependencies {
 
    implementation "com.kakao.sdk:v2-navi:2.4.2" // 카카오내비
    implementation group: 'com.kakao.sdk', name: 'kakaonavi', version: '1.30.6'
 
}
 

 

SDK 사용해서 키 해시 구하기

카카오 SDK를 통해서도 키 해시 확인이 가능하다.

 

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    context = this@MainActivity
 
    // 카카오 내비 hashKey 등록을 위해 사용
    val keyHash = com.kakao.util.helper.Utility.getKeyHash(this /* context */)
    Log.d("Hash",keyHash)
}

 

 

※ 개발용 hash 키를 위 코드로 쉽게 Log 확인한 다음에 사이트에 등록했을 때는 잘 되었는데....

Release hash 키 등록을 윈도우 PC용으로 구해서 하니까 다른 코드가 나온다.

뭔가 잘못을 한 것인지 카카오내비가 동작하지 않았다.

그래서 아래 코드를 팝업창으로 띄워서 확인했더니 다른 키값을 반환한다.

이 코드로 생성된 hash 를 사이트에 등록하니 카카오내비가 정상적으로 구동된다.

// 카카오 내비 hashKey 등록을 위해 사용
val keyHash = com.kakao.util.helper.Utility.getKeyHash(this /* context */)
val message = keyHash
val builder = AlertDialog.Builder(context)
builder.setTitle("Release Key Hash")
builder.setMessage(message)
    .setCancelable(false)
    .setPositiveButton("OK") { dialog, id ->
        dialog.dismiss()
    }
val alert = builder.create()
alert.show()
 

 

 

블로그 이미지

Link2Me

,