728x90

HashMap ketSet<String> 을 String 배열로 만들는 법을 적어둔다.

String[] mKeys = map.keySet().toArray(new String[map.size()]);

를 활용할 기회가 있었다.

key 로부터 map.get(key); 로 값을 얻을 수 있다.


key 를 배열로 만들어야 value를 얻을 수 있을 때 아래 예제를 살펴보면 도움이 될 것이다.


import java.util.HashMap;
import java.util.Map;

public class HashMap_Test {

    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("1", "대조영");
        map.put("2", "이순신");
        map.put("4", "강감찬");
        map.put("3", "양만춘");
        map.put("5", "을지문덕");
        map.put("3", "연개소문"); // HashMap 은 key 가 같고 값이 다르면 value를 덮어쓰기를 한다.
       
        // Getting the value of "4" key.
        System.out.println("The Value is: " + map.get("4"));

        map.containsKey("강아지"); // key 가 존재하면 true 반환
        map.containsValue("조나비"); // value 가 존재하면 true 반환

        // Using keySet() to get the set view of keys
        System.out.println("The set is: " + map.keySet());
       
        String[] mKeys = map.keySet().toArray(new String[map.size()]);
        for (String key : mKeys) {
            System.out.println(key);
        }
       
        for(int i=0; i < mKeys.length;i++) {
            System.out.println("key :" + mKeys[i] + ", value : " + map.get(mKeys[i]));
        }
    }

}
 


HashMap의 경우 단점이 put을 통해 데이터나 객체를 넣을때 key의 순서가 지켜지지 않는다는 것이다.
개발을 할때 코드상으로 순차적으로 key/value를 넣어도, 실제 HashMap에서는 해당 순서가 지켜지지 않는다.
만약 입력된 Key의 순서가 보장되어야 한다면 LinkedHashMap을 사용하면 된다.

LinkedHashMap 은 기본적으로 HashMap을 상속받아 만들어져 있게 때문에 HashMap의 기능을 그대로 사용 가능하다.
대신 여기에 순서라는 개념이 들어 갔다.



참고하면 도움이 될 자료

https://stackoverflow.com/questions/5234576/what-adapter-shall-i-use-to-use-hashmap-in-a-listview


728x90
블로그 이미지

Link2Me

,
728x90

Hashmap 을 ArrayList 로 변환하는 방법에 대한 코드 예제가 있어서 약간 추가해서 테스트해보고 적어둔다.

HashMap 은 Map 인터페이스의 한 종류로 key 와 value 를 묶어서 하나의 entry로 저장한다는 특징을 가지고 있다.

key 값은 중복이 불가능하고 value 값은 중복이 가능하며, null 값도 사용 가능하다.

해싱 기법으로 데이터를 저장하고, 데이터가 많아도 검색이 빠르다.

멀티쓰레드에서 동시에 HashMap key - value 를 사용하면 문제가 될 수 있다.

멀티쓰레드에서는 HashTable을 쓴다.


HashMap 을 ArrayList 로 변환하는 3가지 방법이 있다.

HashMap key 를 ArrayList 로 변환, HashMap value 를 ArrayList 로 변환, HashMap key-value 를 ArrayList 로 변환할 수 있다.

Iterator 인터페이스를 사용할 수 없는 컬렉션인 Map 에서 Iterator 인터페이스를 사용하기 위해서는 Map 에 keySet(), entrySet() 메소드를 사용하여 Set 객체를 반환한 후 Iterator 인터페이스를 사용하면 된다.


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

public class HashMapToArrayList {

    public static void main(String[] args) {
        //Creating a HashMap object
        HashMap<String, String> map = new HashMap<String, String>();

        //Adding elements to HashMap
        map.put("1", "이순신");
        map.put("2", "강감찬");
        map.put("5", "주몽");
        map.put("6", "대조영");
        map.put("3", "을지문덕");
        map.put("4", "연개소문");

        // Getting Set of keys
        Set<String> keySet = map.keySet();

        // ArrayList 의 key 생성
        ArrayList<String> listOfKeys = new ArrayList<String>(keySet);
        System.out.println("ArrayList Of Keys :");
        for (String key : listOfKeys) {
            System.out.println(key);
        }
        System.out.println("--------------------------");

        // Getting Collection of values
        Collection<String> values = map.values();

        // ArrayList 의 값 생성
        ArrayList<String> listOfValues = new ArrayList<String>(values);
        System.out.println("ArrayList Of Values :");
        for (String value : listOfValues) {
            System.out.println(value);
        }
        System.out.println("--------------------------");

        //Getting the Set of entries
        Set<Entry<String, String>> entrySet = map.entrySet();

        // ArrayList 의  Entry objects 생성
        ArrayList<Entry<String, String>> listOfEntry = new ArrayList<Entry<String,String>>(entrySet);
        System.out.println("ArrayList of Key-Values :");
        for (Entry<String, String> entry : listOfEntry) {
            System.out.println(entry.getKey()+" : "+entry.getValue());
        }


        // entrySet().iterator()
        Iterator<Map.Entry<String,String>> iteratorE = map.entrySet().iterator();
        System.out.println("------- Iterator<Map.Entry<String,String>> -----------");
        while(iteratorE.hasNext()) {
            Map.Entry<String, String> entry = (Map.Entry<String, String>) iteratorE.next();
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key : " + key + " , value : "+value);
        }
       
        // 방법 05 : keySet().iterator()
        Iterator<String> iteratorK = map.keySet().iterator();
        System.out.println("------- Iterator<String> iteratorK -----------------");
        while (iteratorK.hasNext()) {
            String key = iteratorK.next();
            String value = map.get(key);
            System.out.println("key : " + key + ", value : " + value);
        }

    }
}


참고하면 도움이 될 자료

ArrayList and HashMap : https://link2me.tistory.com/1210

https://javaconceptoftheday.com/convert-hashmap-to-arraylist-in-java/


Map 동영상 강좌

https://programmers.co.kr/learn/courses/9/lessons/260#

Java의 정석, 11. 컬렉션 프레임웍 : https://www.youtube.com/watch?v=y3qLfTOmONE


728x90
블로그 이미지

Link2Me

,
728x90

안드로이드 달력 만들기 MainActivity.java 와 GridCellAdapter.java 파일에서 아래와 같이 ArrayList 제네릭으로 HashMap<String, String> 을 사용하는 방법이다.


ArrayList<Calendar_Item> calList = new ArrayList<>();

ArrayList<HashMap<String, String>> calList = new ArrayList<>();

로 변경하여 코딩하면 어떻게 처리해야 할까?


Calendar_Item 이 HashMap<String, String> 라는 것으로 변경된 것만 살펴보고 수정해주면 된다.


    private Calendar_Item calendarItem(String year, String month, String day, int weekday, String color, String name, String key){
        Calendar_Item item = new Calendar_Item();
        item.setYear(year);
        item.setMonth(month);
        item.setDay(day);
        item.setWeekday(weekday);
        item.setColor(color);
        item.setEvent(name);
        item.setKey(key);
        return item;
    }

    private void addCalendarItem(String year, String month, String day, int weekday, String color,String name, String key){
        Calendar_Item item = calendarItem(year,month,day,weekday,color,name,key);
        calList.add(item);
    }

    private int getIndexOfCalList(String search_key) {
        for (int temp = 0; temp < calList.size(); temp++) {
            String key = calList.get(temp).getKey();
            if (key != null && key.equals(search_key)) {
                return temp;
            }
        }
        return -1;
    }
 

    private HashMap<String, String> calendarItem(String year, String month, String day, int weekday, String color, String name, String key){
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("year",year);
        map.put("month",month);
        map.put("day",day);
        map.put("weekday", String.valueOf(weekday));
        map.put("color",color);
        map.put("event",name);
        map.put("key",key);
        return map;
    }

    private void addCalendarItem(String year, String month, String day, int weekday, String color,String name, String key){
        HashMap<String, String> item = calendarItem(year,month,day,weekday,color,name,key);
        calList.add(item);
    }

    private int getIndexOfCalList(String search_key) {
        for (int temp = 0; temp < calList.size(); temp++) {
            String key = calList.get(temp).get("key");
            if (key != null && key.equals(search_key)) {
                return temp;
            }
        }
        return -1;
    }


GridCellAdapter 에서 변경해줄 것은 아래와 같다.

    private final ArrayList<Calendar_Item> calList;


    public GridCellAdapter(Context context, ArrayList<Calendar_Item> list) {
        mContext = context;
        calList = list;
    }

    public Calendar_Item getItem(int position) {
        return calList.get(position);
    }


        String theday = calList.get(position).getDay();
        int themonth = Integer.parseInt(calList.get(position).getMonth());
        String theyear = calList.get(position).getYear();
        String holiday = calList.get(position).getEvent();
 

    private final ArrayList<HashMap<String, String>> calList;


    public GridCellAdapter(Context context, ArrayList<HashMap<String, String>> list) {
        mContext = context;
        calList = list;
    }


    public HashMap<String, String> getItem(int position) {
        return calList.get(position);
    }

        String theday = calList.get(position).get("day");
        int themonth = Integer.parseInt(calList.get(position).get("month"));
        String theyear = calList.get(position).get("year");
        String holiday = calList.get(position).get("event");
 


ArrayList<HashMap<String, String>> 를 사용하면 아래와 같은 Calendar_Item Class 를 만들 필요가 없어 코드가 좀 더 간단해질 수 있다.

public class Calendar_Item {
    private  String year; // 년
    private  String month; // 월
    private  String day; // 일
    private  int weekday; // 요일
    private  String color; // 색상
    private String event;
    private String key; // 월일 값을 key 로 사용

    public Calendar_Item() {
    }

    public Calendar_Item(String year, String month, String day, int weekday, String color) {
        this.year = year;
        this.month = month;
        this.day = day;
        this.weekday = weekday;
        this.color = color;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }

    public int getWeekday() {
        return weekday;
    }

    public void setWeekday(int weekday) {
        this.weekday = weekday;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getEvent() {
        return event;
    }

    public void setEvent(String event) {
        this.event = event;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}


728x90
블로그 이미지

Link2Me

,
728x90

새로운 Moudle 를 추가했더니 이런 에러가 발생한다.

Manifest merger failed with multiple errors, see logs


보통 다른 블로그에 나온 경우와는 다른 황당한 경우다.


public class Calendar_Item {
    private  String year; // 년
    private  String month; // 월
    private  String day; // 일
    private  int weekday; // 요일
    private  String color; // 색상
    private String event;
    private String key; // 월일 값을 key 로 사용


처음에 이름을 name 으로 했다가 rename 으로 일괄 event 로 변경했더니......

프로젝트내의 모든 AndroidManifest.xml 파일과

res/values/ 하단 파일들이 모두 name → event 로 변경되어 있더라. ㅠㅠ


이런 황당한 경우가 있다니....

그러다보니 AndroidManifest.xml 이 비정상적이다보니 이런 에러메시지를 보여줬던 거다.


모두 수작업으로 EditPlus 에서 event= → name= 로 변경해주고 나서야 정상이 되었다.


변수를 사용할 때 절대로 name 과 같은 걸 사용해서는 안되겠다는 걸 알게 되었다.



728x90
블로그 이미지

Link2Me

,
728x90

Calendar Class 를 사용하여 달력을 만드는데 알아둘 사항을 정리했다.

Date는 JDK1.0, Calendar는 JDK1.1 부터 제공되었다.

Calendar는 추상클래스라서 객체를 직접 생성할 수 없고, 메소드를 통해서 완전히 구현된 클래스의 인스턴스를 얻어야 한다.

Calendar cal = new Calendar(); // 에러. 추상클래스는 인스턴스를 생성할 수 없다.

Calendar cal = Calendar.getInstance(); // getInstance()는 Calendar 클래스의 인스턴스를 반환


아래 코드는 Java에서 작성한 코드다.

package Calendar;

import java.util.Calendar;

public class Calendar_EX {

    public static void main(String[] args) {
        // Calendar Class 를 사용한 달력 구현을 위한 기본사항 알아보기
        int year = 2019;
        int month = 2;
        int trailingSpaces = 0;
        int daysInPrevMonth = 0;
        int prevYear = 0;
        int prevMonth = 0;
        int DAY_OFFSET = 1;
       
        Calendar cal = Calendar.getInstance(); //Calendar 객체 생성(시스템의 현재 날짜와 시간 정보)
        System.out.println("today is "+cal.getTime()); // 현재시각을 알려주는 메소드
        System.out.println("올해 년도 : " + cal.get(Calendar.YEAR));
        System.out.println("이번 달(0~11, 0:1월): " + cal.get(Calendar.MONTH));
        System.out.println("이번 달의 오늘 날짜 : " + cal.get(Calendar.DATE));
       
        int LastDays = cal.getActualMaximum(Calendar.DATE); //해당 월 마지막 날짜
        System.out.println("이번달의 마지막날짜 : "+LastDays); // 현재시각을 알려주는 메소드
        int dayofweek = cal.get(Calendar.DAY_OF_WEEK); // 현재 요일 (일요일은 1, 토요일은 7)
        System.out.println("이번달 오늘날짜 요일 값 : "+dayofweek); // 오늘날짜의 요일값을 반환
       
        System.out.println("이번년도의 몇째 주 : "+ cal.get(Calendar.WEEK_OF_YEAR));
        System.out.println("이번달의 몇째 주 : "+ cal.get(Calendar.WEEK_OF_MONTH));
        System.out.println("이번달의 몇째 주 : " + cal.get(Calendar.DAY_OF_WEEK_IN_MONTH));
        System.out.println("현재 시간 : "+ cal.get(Calendar.HOUR)); // 현재 시간 (12시간제)
        System.out.println("현재 시간 : "+ cal.get(Calendar.HOUR_OF_DAY)); // 현재 시간 (24시간제)
        System.out.println("오전_오후(0:오전, 1:오후): " + cal.get(Calendar.AM_PM));
        System.out.println("현재 분 : "+ cal.get(Calendar.MINUTE));
        System.out.println("현재 초 : "+ cal.get(Calendar.SECOND));
        String time = String.valueOf(cal.get(Calendar.HOUR)) + "시 " +
                String.valueOf(cal.get(Calendar.MINUTE)) + "분 " +
                String.valueOf(cal.get(Calendar.SECOND)) + "초" ;
        System.out.println("현재 시간 : "+ time);
       
       
        //set메소드를 통해 연도 월 일 을 설정
        cal.set(Calendar.YEAR, Integer.valueOf(year));
        cal.set(Calendar.MONTH, Integer.valueOf(month)-1); // 0 ~ 11월이라  1을 빼줌
        cal.set(Calendar.DAY_OF_MONTH, 1); // 지정 월의 날짜
        // 년 월 일을 나누어서 표현했고, 아래는 하나로 년월일을 지정
        //cal.set(year,month-1,1); // 0 ~ 11월로 인식하므로 - 1, 입력된 년월

        LastDays = cal.getActualMaximum(Calendar.DATE); //지정한 월의 마지막 날짜
        System.out.println("SET 지정달 마지막날짜 : "+LastDays);

        dayofweek = cal.get(Calendar.DAY_OF_WEEK); // 지정날짜 요일 (일요일은 1, 토요일은 7)
        System.out.println("SET 지정달 week 값 : "+dayofweek); // 지정날짜의 1일의 요일 값을 표시
        trailingSpaces = dayofweek -1;
        prevMonth = month -1;
        prevYear = year;
        daysInPrevMonth = getLastDay(prevMonth,prevYear);
        System.out.println("SET 이전달 마지막날짜 : "+daysInPrevMonth);
        // 이번달 1일 이전 공백 부분을 채우기 위한 이전달 날짜
        for (int i = 0; i < trailingSpaces; i++) {
             System.out.println("SET 이전달 날짜 : "+ String.valueOf((daysInPrevMonth - trailingSpaces + DAY_OFFSET) + i));
        }
    }

    // 달의 마지막 일을 구함
    private static int getLastDay(int month, int year) {
        if (month == 4 || month == 6 || month == 9 || month == 11) {
            return 30;
        } else if (month == 2 && isLeapYear(year) == true) {
            return 29;
        } else if (month == 2 && isLeapYear(year) == false) {
            return 28;
        } else {
            return 31;
        }
    }

    // 해당 년도가 윤년인지 판별
    private static boolean isLeapYear(int year) {
        return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0));
    }
}


728x90
블로그 이미지

Link2Me

,
728x90

안드로이드 달력 화면에 뿌려줄 GridCellAdapter 코드는 아래와 같다.


package com.link2me.android.adpater;

import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.link2me.android.calendar.R;
import com.link2me.android.item.Calendar_Item;
import com.link2me.android.util.CalendarHelper;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

public class GridCellAdapter extends BaseAdapter implements View.OnClickListener {
    private static final String tag = GridCellAdapter.class.getSimpleName();
    private final Context mContext;

    private final ArrayList<Calendar_Item> calList;
    private LinearLayout gridcell_layout;
    private TextView gridcell_Day;
    private TextView gridcell_LunarDay;
    private TextView gridcell_Event;
    private final SimpleDateFormat dateFormatter = new SimpleDateFormat("dd-MMM-yyyy");

    public GridCellAdapter(Context context, ArrayList<Calendar_Item> list) {
        mContext = context;
        calList = list;
    }

    public Calendar_Item getItem(int position) {
        return calList.get(position);
    }

    @Override
    public int getCount() {
        return calList.size();
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;
        if (view == null) {
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.calendar_cell, parent, false);
        }

        // Get a reference to the Day gridcell_layout
        gridcell_layout = (LinearLayout) view.findViewById(R.id.calendar_day_gridcell);
        gridcell_Day = view.findViewById(R.id.dayTV);
        gridcell_LunarDay = view.findViewById(R.id.lunardayTV);
        gridcell_Event = view.findViewById(R.id.eventTV);
        gridcell_layout.setOnClickListener(this);

        String theday = calList.get(position).getDay();
        int themonth = Integer.parseInt(calList.get(position).getMonth());
        String theyear = calList.get(position).getYear();
        String holiday = calList.get(position).getEvent();

        // Set the Day GridCell
        gridcell_Day.setText(theday);
        gridcell_LunarDay.setText(CalendarHelper.Sol2Lun(theyear, String.valueOf(themonth-1),theday)); //0 ~ 11월로 인식하므로 - 1
        if(holiday.length()>0){
            gridcell_Event.setText(holiday);
        } else {
            gridcell_Event.setText("");
        }
        gridcell_layout.setTag(theday + "-" + themonth + "-" + theyear);

        if(calList.get(position).getColor().equals("GRAY")){
            gridcell_Day.setTextColor(Color.LTGRAY);
        }
        if(calList.get(position).getColor().equals("BLACK")){
            gridcell_Day.setTextColor(Color.BLACK);
        }
        if(calList.get(position).getColor().equals("RED")){
            gridcell_Day.setTextColor(Color.RED);
            gridcell_Event.setTextColor(Color.RED);
        }
        if(calList.get(position).getColor().equals("BLUE")){
            gridcell_Day.setTextColor(Color.BLUE);
        }
        if(calList.get(position).getColor().equals("CYAN")){
            gridcell_layout.setBackgroundColor(Color.CYAN);
        }
        return view;
    }

    @Override
    public void onClick(View view) {
        String date_month_year = (String) view.getTag();
        Log.e("Selected date", date_month_year);
        try {
            Date parsedDate = dateFormatter.parse(date_month_year);
            Log.d(tag, "Parsed Date: " + parsedDate.toString());

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

}


calendar_cell.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/calendar_day_gridcell"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/day_cell_bg"
    android:orientation="vertical"
    android:padding="7dp">

    <TextView
        android:id="@+id/dayTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="8"
        android:textStyle="bold"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/lunardayTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:text="00.16"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/eventTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:text="기념일"
        android:textColor="#ff0000" />

</LinearLayout>



728x90
블로그 이미지

Link2Me

,
728x90

CalendarHelper Class 를 생성하여 양력/음력 변환, 음력/양력 변환, 한달의 마지막날짜 구하기 등을 하는 메소드를 구현한다.


package com.link2me.android.util;

import com.ibm.icu.util.ChineseCalendar;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CalendarHelper {
    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    public static String Lunar2Solar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));

        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }

    /**
     * 양력날짜를 음력날짜로 변환
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력날짜 (yyyyMMdd)
     */
    public static String Solar2Lunar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim() ;
        if( date.length() != 8 ) {
            if( date.length() == 4 )
                date = date + "0101" ;
            else if( date.length() == 6 )
                date = date + "01" ;
            else if( date.length() > 8 )
                date = date.substring(0,8) ;
            else
                return "" ;
        }

        cal.set( Calendar.YEAR, Integer.parseInt(date.substring(0,4)) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(date.substring(4,6))-1 ) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)) ) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString() ;
    }

    public static int WeekendValue(String date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar cal = Calendar.getInstance();
        cal.setTime(sdf.parse(date));
        return cal.get(Calendar.DAY_OF_WEEK);
        // Calendar.SUNDAY : 1
        // Calendar.SATURDAY : 7
    }

    /**
     * 양력날짜의 요일을 리턴
     * @param 양력날짜 (yyyyMMdd)
     * @return 요일(int)
     */
    public static int getDayOfWeek(String day) {
        int y = Integer.parseInt(day.substring(0, 4));
        int m = Integer.parseInt(day.substring(4, 6)) - 1;
        int d = Integer.parseInt(day.substring(6));
        Calendar c = Calendar.getInstance();
        c.set(y, m, d);
        return c.get(Calendar.DAY_OF_WEEK);
    }

    public static String Sol2Lun(String year, String month, String day) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (year == null || month == null || day == null)
            return "";

        cal.set( Calendar.YEAR, Integer.parseInt(year) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(month)) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(day)) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString().substring(4,6)+"."+ret.toString().substring(6,8);
    }

    // 해당 달의 첫 요일을 구해서 돌려줌.
    private static int getStartDay(int year, int month) {
        int monthSum = 0;
        int leapYear = 0;
        int daySum = 1;

        for (int i = 1; i < year; i++) {
            monthSum += 365;
            if (isLeapYear(i) == true) {
                leapYear += 1;
            }
        }

        for (int j = 1; j < month; j++) {
            daySum += getLastDay(year, j);
        }

        return (monthSum + leapYear + daySum) % 7;

    }

    // 달의 마지막 일을 구함
    public static int getLastDay(int month, int year) {

        if (month == 4 || month == 6 || month == 9 || month == 11) {
            return 30;
        } else if (month == 2 && isLeapYear(year) == true) {
            return 29;
        } else if (month == 2 && isLeapYear(year) == false) {
            return 28;
        } else {
            return 31;
        }
    }

    // 해당 년도가 윤년인지 판별
    private static boolean isLeapYear(int year) {
        return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0));
    }
}


CalendarHelper 수정본

package com.link2me.android.util;

import com.ibm.icu.util.ChineseCalendar;
import com.link2me.android.item.Holidays_Item;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;


public class CalendarHelper {
    static HashMap<String, Holidays_Item> holidaysArrayList = new HashMap<String,Holidays_Item>();

    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    public static String Lunar2Solar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));

        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }

    /**
     * 양력날짜를 음력날짜로 변환
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력날짜 (yyyyMMdd)
     */
    public static String Solar2Lunar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim() ;
        if( date.length() != 8 ) {
            if( date.length() == 4 )
                date = date + "0101" ;
            else if( date.length() == 6 )
                date = date + "01" ;
            else if( date.length() > 8 )
                date = date.substring(0,8) ;
            else
                return "" ;
        }

        cal.set( Calendar.YEAR, Integer.parseInt(date.substring(0,4)) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(date.substring(4,6))-1 ) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)) ) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString() ;
    }


    public static HashMap<String,Holidays_Item> holidayArray(String yyyy){
        holidaysArrayList.clear(); // 데이터 초기화
        // 양력 휴일
        addHolidaysItem(yyyy,"0101" ,"신정");
        addHolidaysItem(yyyy,"0301" ,"삼일절");
        addHolidaysItem(yyyy,"0505" ,"어린이날");
        addHolidaysItem(yyyy,"0606" ,"현충일" );
        addHolidaysItem(yyyy,"0815" ,"광복절");
        addHolidaysItem(yyyy,"1003" ,"개천절");
        addHolidaysItem(yyyy,"1009" ,"한글날");
        addHolidaysItem(yyyy,"1225" ,"성탄절");

        // 음력 휴일
        String prev_seol = String.valueOf(Integer.parseInt(Lunar2Solar(yyyy+"0101")) -1);
        addHolidaysItem(yyyy,prev_seol.substring(4) ,"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0101"),"설날");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0102"),"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0408"),"석탄일");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0814"),"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0815"),"추석");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0816"),"");

        try {
            // 어린이날 대체공휴일 검사 : 어린이날은 토요일, 일요일인 경우 그 다음 평일을 대체공유일로 지정
            int childDayChk = WeekendValue(yyyy+"0505");
            if(childDayChk == 1) addHolidaysItem(yyyy,"0506" ,"대체공휴일");
            if(childDayChk == 7) addHolidaysItem(yyyy,"0507" ,"대체공휴일");

            // 설날 대체공휴일 검사
            if(WeekendValue(Lunar2Solar(yyyy+"0101"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0101"))==2) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0102"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");

            // 추석 대체공휴일 검사
            if(WeekendValue(Lunar2Solar(yyyy+"0814"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0815"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0816"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
        } catch (ParseException e) {
            e.printStackTrace();
        }

        //Collections.sort(holidaysArrayList); // 오름차순 정렬

        return holidaysArrayList;
    }

    private static String SolarDays(String yyyy, String date){
        return Lunar2Solar(yyyy+date).substring(4);
    }

    private static void addHolidaysItem(String year, String date, String name ){
        Holidays_Item item = new Holidays_Item();
        item.setYear(year);
        item.setDate(date);
        item.setName(name);
        holidaysArrayList.put(date,item);
    }

    public static int WeekendValue(String date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar cal = Calendar.getInstance();
        cal.setTime(sdf.parse(date));
        return cal.get(Calendar.DAY_OF_WEEK);
        // Calendar.SUNDAY : 1
        // Calendar.SATURDAY : 7
    }

    /**
     * 양력날짜의 요일을 리턴
     * @param 양력날짜 (yyyyMMdd)
     * @return 요일(int)
     */
    public static int getDayOfWeek(String day) {
        int y = Integer.parseInt(day.substring(0, 4));
        int m = Integer.parseInt(day.substring(4, 6)) - 1;
        int d = Integer.parseInt(day.substring(6));
        Calendar c = Calendar.getInstance();
        c.set(y, m, d);
        return c.get(Calendar.DAY_OF_WEEK);
    }

    public static String Sol2Lun(String year, String month, String day) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (year == null || month == null || day == null)
            return "";

        cal.set( Calendar.YEAR, Integer.parseInt(year) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(month)) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(day)) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString().substring(4,6)+"."+ret.toString().substring(6,8);
    }

    // 해당 달의 첫 요일을 구해서 돌려줌.
    private static int getStartDay(int year, int month) {
        int monthSum = 0;
        int leapYear = 0;
        int daySum = 1;

        for (int i = 1; i < year; i++) {
            monthSum += 365;
            if (isLeapYear(i) == true) {
                leapYear += 1;
            }
        }

        for (int j = 1; j < month; j++) {
            daySum += getLastDay(year, j);
        }

        return (monthSum + leapYear + daySum) % 7;

    }

    // 달의 마지막 일을 구함
    public static int getLastDay(int month, int year) {

        if (month == 4 || month == 6 || month == 9 || month == 11) {
            return 30;
        } else if (month == 2 && isLeapYear(year) == true) {
            return 29;
        } else if (month == 2 && isLeapYear(year) == false) {
            return 28;
        } else {
            return 31;
        }
    }

    // 해당 년도가 윤년인지 판별
    private static boolean isLeapYear(int year) {
        return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0));
    }



728x90
블로그 이미지

Link2Me

,
728x90

달력 데이터를 생성하는 것은 ArrayList 또는 Hashmap 을 이용한다.


먼저 달력 데이터 Class 를 구현한다.

package com.link2me.android.item;

public class Calendar_Item {
    private  String year; // 년
    private  String month; // 월
    private  String day; // 일
    private  int weekday; // 요일
    private  String color; // 색상
    private String event;
    private String key; // 월일 값을 key 로 사용

    public Calendar_Item() {
    }

    public Calendar_Item(String year, String month, String day, int weekday, String color) {
        this.year = year;
        this.month = month;
        this.day = day;
        this.weekday = weekday;
        this.color = color;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }

    public int getWeekday() {
        return weekday;
    }

    public void setWeekday(int weekday) {
        this.weekday = weekday;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getEvent() {
        return event;
    }

    public void setEvent(String event) {
        this.event = event;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}


먼저 작성했던 MainActivity 에서 makeCalendarList(int CurrentMonth, int yy) 메소드를 추가하면서 필요한 코드를 작성한다.

여기서는 공휴일 데이터를 추가하는 루틴을 생략했다.

공휴일 데이터 추가 샘플은 https://link2me.tistory.com/1699 게시글과 다음 게시글인 CalendarHelper 를 같이 참조하며 CalendarHelper 에 필요한 사항을 추가하고 수정해서 구현하도록 개발자의 몫으로 남겼다.


class package com.link2me.android.calendar;

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.link2me.android.adpater.GridCellAdapter;
import com.link2me.android.item.Calendar_Item;
import com.link2me.android.item.Holidays_Item;
import com.link2me.android.util.CalendarHelper;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();
    private Context mContext;

    private TextView currentMonth;
    private ImageView prevYear;
    private ImageView prevMonth;
    private ImageView nextMonth;
    private ImageView nextYear;
    private ImageView selectDate;

    private GridView calendarView;
    private GridCellAdapter adapter;
    private Calendar cal;
    private int month, year;
    private static final String dateTemplate = "yyyy MMMM";

    private ArrayList<Calendar_Item> calList = new ArrayList<>();
    private static final int DAY_OFFSET = 1;
    private final String[] weekdays = new String[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
    private int lastDays;

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

        cal = Calendar.getInstance(Locale.getDefault());
        month = cal.get(Calendar.MONTH) + 1;
        year = cal.get(Calendar.YEAR);

        prevYear = (ImageView) this.findViewById(R.id.prevYear);
        prevYear.setOnClickListener(this);

        prevMonth = (ImageView) this.findViewById(R.id.prevMonth);
        prevMonth.setOnClickListener(this);

        currentMonth = (TextView) this.findViewById(R.id.currentMonth);
        currentMonth.setText(year +"년 " + month + "월");
        currentMonth.setOnClickListener(this);

        nextMonth = (ImageView) this.findViewById(R.id.nextMonth);
        nextMonth.setOnClickListener(this);

        nextYear = (ImageView) this.findViewById(R.id.nextYear);
        nextYear.setOnClickListener(this);

        selectDate = (ImageView) this.findViewById(R.id.selectDate);
        selectDate.setOnClickListener(this);

        calendarView = (GridView) this.findViewById(R.id.calendar);

        makeCalendarList(month, year);

        // Initialised
        adapter = new GridCellAdapter(mContext, calList);
        adapter.notifyDataSetChanged();
        calendarView.setAdapter(adapter);
    }

    private void setGridCellAdapterToDate(int month, int year) {
        makeCalendarList(month, year);
        adapter = new GridCellAdapter(mContext, calList);
        adapter.notifyDataSetChanged();
        calendarView.setAdapter(adapter);
        // 상단 화면 날짜 출력
        currentMonth.setText(year +"년 " + month + "월");
    }

    private void makeCalendarList(int CurrentMonth, int yy) {
        int trailingSpaces = 0;
        int daysInPrevMonth = 0;
        int prevMonth = 0;
        int prevYear = 0;
        int nextMonth = 0;
        int nextYear = 0;

        calList.clear();

        Calendar cal = Calendar.getInstance();

        int thisYear = cal.get(Calendar.YEAR);

        int thisMonth = cal.get(Calendar.MONTH); // 현재 월
        int thisDay = cal.get(Calendar.DAY_OF_MONTH); // 오늘 날짜
        cal.set(yy,CurrentMonth-1,1); // 0 ~ 11월로 인식하므로 - 1, 입력된 년월
        Log.e(TAG, "Calendar SET := " + cal.getTime().toString());
        lastDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);

        if (CurrentMonth == 12) {
            prevMonth = CurrentMonth - 1;
            nextMonth = 1;
            prevYear = yy;
            nextYear = yy + 1;
            daysInPrevMonth = CalendarHelper.getLastDay(prevMonth,prevYear);
        } else if (CurrentMonth == 1) {
            prevMonth = 12;
            prevYear = yy - 1;
            nextYear = yy;
            nextMonth = CurrentMonth + 1;
            daysInPrevMonth = CalendarHelper.getLastDay(prevMonth,prevYear);
        } else {
            prevMonth = CurrentMonth - 1;
            nextMonth = CurrentMonth + 1;
            nextYear = yy;
            prevYear = yy;
            daysInPrevMonth = CalendarHelper.getLastDay(prevMonth,prevYear);
        }

        int currentWeekDay = cal.get(Calendar.DAY_OF_WEEK) - 1;
        trailingSpaces = currentWeekDay;

        // 이전월 데이터 채우기 위한 리스트 추가
        for (int i = 0; i < trailingSpaces; i++) {
            String key = String.format("%02d",prevMonth)+String.format("%02d",(daysInPrevMonth - trailingSpaces + DAY_OFFSET) + i);
            addCalendarItem(String.valueOf(prevYear),String.valueOf(prevMonth),String.valueOf((daysInPrevMonth - trailingSpaces + DAY_OFFSET) + i),
                    0,"GRAY","",key);
        }

        // 현재월 데이터 리스트 추가
        for (int i = 1; i <= lastDays; i++) {
            String date = String.valueOf(yy)+String.format("%02d",CurrentMonth)+String.format("%02d",i);
            String key = String.format("%02d",CurrentMonth)+String.format("%02d",i);
            try {
                int weekday = CalendarHelper.WeekendValue(date);
                if(weekday == 1) // 일요일
                    addCalendarItem(String.valueOf(yy),String.valueOf(CurrentMonth),String.valueOf(i),weekday,"RED","",key);
                else if(weekday == 7) // 토요일
                    addCalendarItem(String.valueOf(yy),String.valueOf(CurrentMonth),String.valueOf(i),weekday,"BLUE","",key);
                else
                    addCalendarItem(String.valueOf(yy),String.valueOf(CurrentMonth),String.valueOf(i),weekday,"BLACK","",key);
                if(CurrentMonth-1 == thisMonth){ // 현재월이면
                    if(i == thisDay){
                        Log.e(TAG, "key := " + key);
                        int index = getIndexOfCalList(key);
                        Calendar_Item item = calendarItem(String.valueOf(yy),String.valueOf(CurrentMonth),String.valueOf(i),weekday,"CYAN","",key);
                        calList.set(index,item);
                    }
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }

        // 다음달 데이터 채우기 리스트 추가
        for (int i = 0; i < calList.size() % 7; i++) {
            String key = String.format("%02d",nextMonth)+String.format("%02d",i+1);
            addCalendarItem(String.valueOf(nextYear),String.valueOf(nextMonth),String.valueOf(i+1),0,"GRAY",
                    "",key);
        }

    }

    private Calendar_Item calendarItem(String year, String month, String day, int weekday, String color, String name, String key){
        Calendar_Item item = new Calendar_Item();
        item.setYear(year);
        item.setMonth(month);
        item.setDay(day);
        item.setWeekday(weekday);
        item.setColor(color);
        item.setEvent(name);
        item.setKey(key);
        return item;
    }

    private void addCalendarItem(String year, String month, String day, int weekday, String color,String name, String key){
        Calendar_Item item = calendarItem(year,month,day,weekday,color,name,key);
        calList.add(item);
    }

    private int getIndexOfCalList(String search_key) {
        for (int temp = 0; temp < calList.size(); temp++) {
            String key = calList.get(temp).getKey();
            if (key != null && key.equals(search_key)) {
                return temp;
            }
        }
        return -1;
    }

    private String getWeekDayAsString(int i) {
        return weekdays[i];
    }
}


참고 도움될 자료 : https://www.toptal.com/android/android-customization-how-to-build-a-ui-component-that-does-what-you-want


다음은 CalendarHelper Class 를 만들어서 음력/양력 변환, 공휴일 처리, 한달의 마지막 날짜 구하기 등 메소드를 구현한다.

728x90
블로그 이미지

Link2Me

,
728x90

Layout 을 화면에 뿌릴 MainActivity.java 코드를 아래와 같이 구현했다.


package com.link2me.android.calendar;

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.link2me.android.adpater.GridCellAdapter;

import java.util.Calendar;
import java.util.Locale;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();
    private Context mContext;

    private TextView currentMonth;
    private ImageView prevYear;
    private ImageView prevMonth;
    private ImageView nextMonth;
    private ImageView nextYear;
    private ImageView selectDate;

    private GridView calendarView;
    private GridCellAdapter adapter;
    private Calendar cal;
    private int month, year;

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

        cal = Calendar.getInstance(Locale.getDefault());
        month = cal.get(Calendar.MONTH) + 1;
        year = cal.get(Calendar.YEAR);

        prevYear = (ImageView) this.findViewById(R.id.prevYear);
        prevYear.setOnClickListener(this);

        prevMonth = (ImageView) this.findViewById(R.id.prevMonth);
        prevMonth.setOnClickListener(this);

        currentMonth = (TextView) this.findViewById(R.id.currentMonth);
        currentMonth.setText(year +"년 " + month + "월");
        currentMonth.setOnClickListener(this);

        nextMonth = (ImageView) this.findViewById(R.id.nextMonth);
        nextMonth.setOnClickListener(this);

        nextYear = (ImageView) this.findViewById(R.id.nextYear);
        nextYear.setOnClickListener(this);

        selectDate = (ImageView) this.findViewById(R.id.selectDate);
        selectDate.setOnClickListener(this);

        calendarView = (GridView) this.findViewById(R.id.calendar);

        // Initialised
        adapter = new GridCellAdapter(mContext, month, year);
        adapter.notifyDataSetChanged();
        calendarView.setAdapter(adapter);
    }

    private void setGridCellAdapterToDate(int month, int year) {
        adapter = new GridCellAdapter(mContext, month, year);
        adapter.notifyDataSetChanged();
        calendarView.setAdapter(adapter);
        // 상단 화면 날짜 출력
        currentMonth.setText(year +"년 " + month + "월");
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.prevYear:
                year--;
                setGridCellAdapterToDate(month, year);
                break;

            case R.id.prevMonth:
                if (month <= 1) {
                    month = 12;
                    year--;
                } else {
                    month--;
                }
                setGridCellAdapterToDate(month, year);
                break;

            case R.id.currentMonth:
                cal = Calendar.getInstance(Locale.getDefault()); // 현재날짜와 시간으로 설정된다.
                month = cal.get(Calendar.MONTH) + 1; // 0 ~ 11월을 반환하므로 + 1을 해준다.
                year = cal.get(Calendar.YEAR);
                setGridCellAdapterToDate(month, year);
                break;

            case R.id.nextMonth:
                if (month > 11) {
                    month = 1;
                    year++;
                } else {
                    month++;
                }
                setGridCellAdapterToDate(month, year);
                break;

            case R.id.nextYear:
                year++;
                setGridCellAdapterToDate(month, year);
                break;

            case R.id.selectDate:
                LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
                final View layout = inflater.inflate(R.layout.select_calendar_input,null);
                AlertDialog.Builder cal_confirm = new AlertDialog.Builder(mContext);
                cal_confirm.setTitle("검색할 년도 입력");
                cal_confirm.setView(layout);
                final EditText etYear = layout.findViewById(R.id.selectYear);
                final EditText etMonth = layout.findViewById(R.id.selectMonth);
                // 확인 버튼 설정
                cal_confirm.setPositiveButton("검색", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        String selectYear = etYear.getText().toString().trim();
                        if(selectYear.length() == 0) {
                            AlertDialog.Builder phoneNO_confirm = new AlertDialog.Builder(mContext);
                            phoneNO_confirm.setMessage("년도를 입력하세요.").setCancelable(false).setPositiveButton("확인",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                                            dialog.dismiss();
                                        }
                                    });
                            AlertDialog alert = phoneNO_confirm.create();
                            alert.show();
                            return;
                        }

                        String selectMonth = etMonth.getText().toString().trim();
                        if(selectMonth.length() == 0) {
                            AlertDialog.Builder phoneNO_confirm = new AlertDialog.Builder(mContext);
                            phoneNO_confirm.setMessage("월(月)을 입력하세요.").setCancelable(false).setPositiveButton("확인",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                                            dialog.dismiss();
                                        }
                                    });
                            AlertDialog alert = phoneNO_confirm.create();
                            alert.show();
                            return;
                        }
                        month = Integer.parseInt(selectMonth);
                        year = Integer.parseInt(selectYear);
                        setGridCellAdapterToDate(month, year);
                    }
                });
                cal_confirm.setNegativeButton("취소", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
                cal_confirm.show();
                break;
        }
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "Destroying View ...");
        super.onDestroy();
    }
}


화면에 출력할 데이터는 GridCellAdapter 를 만들어서 뿌려주면 된다.

728x90
블로그 이미지

Link2Me

,
728x90

만들고 싶은 달력의 모습은 아래와 같다.

이미지 파일은 https://www.iconfinder.com/ 에서 free 이미지를 받아서 이미지 크기를 줄여서 사용했다.


 



AndroidManifest.xml

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.link2me.android.calendar"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

    }

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

}

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

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation files('libs/icu4j-4_8_2.jar')
}


Android 달력 만들기 Layout 구성을 위한 XML 파일 구조다.

전년도, 이전달, 현재 월, 다음달, 다음년도, 특정 년월 검색

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

    <LinearLayout
        android:id="@+id/buttonlayout"
        android:layout_width="fill_parent"
        android:layout_height="60sp"
        android:background="@drawable/navibar_bg"
        android:gravity="left|top"
        android:height="60sp"
        android:orientation="horizontal" >

        <LinearLayout
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center">

            <ImageView
                android:id="@+id/prevYear"
                android:layout_width="wrap_content"
                android:layout_height="35dp"
                android:layout_gravity="center"
                android:src="@drawable/double_arrow_left" >
            </ImageView>
        </LinearLayout>

        <LinearLayout
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center">
            <ImageView
                android:id="@+id/prevMonth"
                android:layout_width="wrap_content"
                android:layout_height="35dp"
                android:layout_gravity="center"
                android:src="@drawable/arrow_left" >
            </ImageView>
        </LinearLayout>

        <TextView
            android:id="@+id/currentMonth"
            android:layout_width="fill_parent"
            android:layout_height="60sp"
            android:layout_weight="0.6"
            android:gravity="center"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="#FFFFFF" >
        </TextView>

        <LinearLayout
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center">

            <ImageView
                android:id="@+id/nextMonth"
                android:layout_width="wrap_content"
                android:layout_height="35dp"
                android:layout_gravity="center"
                android:src="@drawable/arrow_right" >
            </ImageView>

        </LinearLayout>

        <LinearLayout
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center">
            <ImageView
                android:id="@+id/nextYear"
                android:layout_width="wrap_content"
                android:layout_height="35dp"
                android:layout_gravity="center"
                android:src="@drawable/double_arrow_right" >
            </ImageView>
        </LinearLayout>

        <LinearLayout
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center">
            <ImageView
                android:id="@+id/selectDate"
                android:layout_width="wrap_content"
                android:layout_height="35dp"
                android:layout_gravity="center"
                android:src="@drawable/box_arrow" >
            </ImageView>
        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="30dp"
        android:background="#e0e7ee"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="일"
            android:textColor="#ff0000"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="월"
            android:textColor="#969a9b"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="화"
            android:textColor="#969a9b"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="수"
            android:textColor="#969a9b"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="목"
            android:textColor="#969a9b"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="금"
            android:textColor="#969a9b"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="토"
            android:textColor="#ff0000"
            android:textSize="14sp"
            android:textStyle="bold" />
    </LinearLayout>

    <GridView
        android:id="@+id/calendar"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:numColumns="7" >
    </GridView>

</LinearLayout>


select_calendar_input.xml

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:text="년도"
            android:layout_marginLeft="20dp"
            android:textSize="12sp" />

        <EditText
            android:id="@+id/selectYear"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginRight="20dp"
            android:singleLine="true"
            android:inputType="number"
            android:textSize="16sp"
            android:textColor="#000000" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:text="월(月)"
            android:textSize="12sp" />

        <EditText
            android:id="@+id/selectMonth"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginRight="20dp"
            android:singleLine="true"
            android:inputType="number"
            android:textSize="16sp"
            android:textColor="#000000" />

    </LinearLayout>
</LinearLayout>


728x90
블로그 이미지

Link2Me

,
728x90

달력을 구현하다보니 September 와 같은 영문 월에 대한 입력을 받아서 숫자 월을 반환하는 코드가 필요하다.

양력 → 음력으로 날짜를 반환할 목적이라서 숫자 대신에 숫자 문자열로 반환하도록 처리했다.


private static String ConvertMonthString(String month){
    // September 와 같은 입력을 9와 같은 숫자 문자열로 반환
    Date date = null;
    try {
        date = new SimpleDateFormat("MMM", Locale.ENGLISH).parse(month);
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return String.valueOf(cal.get(Calendar.MONTH));
    } catch (ParseException e) {
        e.printStackTrace();
        return "";
    }
}


728x90
블로그 이미지

Link2Me

,
728x90

현재 위치를 문자로 보내기

현재 내 위치를 문자로 상대방에게 보내는 기능을 구현 테스트 해봤다.

2010년대 초반대에 지도가 막 대세로 떠오르던 그 시절에 서비스 기획하면서 내 위치를 상대방에게 보내주면 주변에서 내가 헤메고 있을 때 유용할 거 같았다.

개인정보 등에 민감한 사항이지만 내 위치를 보내주는 것은 괜찮지 않을까 하는 생각이 들었다.

어디까지나 기능 구현 차원에서 접근하는 것이니 이점은 감안하고 참고하시길 ^^


AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- GPS -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.SEND_SMS" />



send_message_popup.xml

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

    <EditText
        android:id="@+id/phoneno"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:singleLine="true"
        android:textSize="16sp"
        android:textColor="#000000" />

</LinearLayout>


https://link2me.tistory.com/1703에서  setCurrentLocation 메소드 부분을 아래와 같이 수정한다.


public void setCurrentLocation(final Location location, String markerTitle, String markerSnippet) {
    if (currentMarker != null) currentMarker.remove();

    LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());

    MarkerOptions markerOptions = new MarkerOptions();
    markerOptions.position(currentLatLng);
    markerOptions.title(markerTitle);
    markerOptions.snippet(markerSnippet);
    markerOptions.draggable(true);

    currentMarker = mMap.addMarker(markerOptions);
    mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
        @Override
        public boolean onMarkerClick(Marker marker) {
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);
            final View layout = inflater.inflate(R.layout.send_message_popup,null);
            AlertDialog.Builder lms_confirm = new AlertDialog.Builder(mContext);
            lms_confirm.setTitle("발송할 휴대폰번호 등록");
            lms_confirm.setView(layout);
            final EditText etphoneNO = (EditText) layout.findViewById(R.id.phoneno);
            // 확인 버튼 설정
            lms_confirm.setPositiveButton("등록", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    String phoneNO = etphoneNO.getText().toString().trim();
                    if(phoneNO.length() == 0) {
                        AlertDialog.Builder phoneNO_confirm = new AlertDialog.Builder(mContext);
                        phoneNO_confirm.setMessage("휴대폰 번호를 입력하세요.").setCancelable(false).setPositiveButton("확인",
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) { // 'YES'
                                        dialog.dismiss();
                                    }
                                });
                        AlertDialog alert = phoneNO_confirm.create();
                        alert.show();
                        return;
                    }
                    String gps_location =String.valueOf(location.getLatitude())+","+String.valueOf(location.getLongitude());
                    SMS_Send(phoneNO,gps_location );
                }
            });
            lms_confirm.setNegativeButton("취소", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });
            lms_confirm.show();
            return true;
        }
    });

    CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng);
    mMap.moveCamera(cameraUpdate);
}

private void SMS_Send(String phoneNO, String message){ // SmsManager API
    String sms_message = "구글 지도 위치를 보내왔습니다.\n";
    sms_message += "http://maps.google.com/maps?f=q&q="+message+"\n"+"누르면 상대방의 위치를 확인할 수 있습니다.";
    Log.e(TAG,"SMS : "+sms_message);
    try {
        //전송
        SmsManager smsManager = SmsManager.getDefault();
        ArrayList<String> parts = smsManager.divideMessage(sms_message);
        smsManager.sendMultipartTextMessage(phoneNO, null, parts, null, null);
        Toast.makeText(getApplicationContext(), "위치전송 문자보내기 완료!", Toast.LENGTH_LONG).show();
    } catch (Exception e) {
        Toast.makeText(getApplicationContext(), "SMS faild, please try again later!", Toast.LENGTH_LONG).show();
        e.printStackTrace();
    }
}


2019.12.15 일 추가 사항

SMS 보내기가 안된다는 댓글 문의가 있어서 적는다.

위험권한에 해당하는 기능은 별도 처리를 해줘야 한다.

https://www.tutorialspoint.com/android/android_sending_sms.htm 에 나온 내용을 참조하거나, https://link2me.tistory.com/1532 에 설명된 TED 퍼미션 사용법을 이용하면 된다.


도움이 되셨다면 ... 해 주세요. 좋은 글 작성에 큰 힘이 됩니다.

728x90
블로그 이미지

Link2Me

,
728x90

구글 제공 구글맵 샘플코드는 https://github.com/googlemaps/android-samples/tree/master 에서 다운로드 받아서 필요한 것을 구현하면 된다.


구글 맵에 현재 위치를 표시하는 걸 Activity 위헤서 동작하는 걸 구현 테스트했다면, 이제 Fragment 상에서 동작되도록 하는 법을 기술한다.


<!-- Activity에서는 fragment로 구현해야 되지만 Fragment로 구현하기위해서는 MapView를 주로 사용-->
<com.google.android.gms.maps.MapView
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.google.android.gms.maps.MapFragment" />


Fragment 생애 주기를 고려해서 처리하지 않으면 배터리 소모나 메모리 누수가 발생할 수 있으니 이런 것까지 고려해서 처리한다.

소스코드를 비교해서 보면 알겠지만 이전 게시글(https://link2me.tistory.com/1703) 코드를 거의 붙여넣기 했는데 들어갈 위치가 좀 다른 것이 있다는 점만 고려하면 된다.



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


    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/tabmenuLayout" />

    <LinearLayout
        android:id="@+id/tabmenuLayout"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true" >

        <Button
            android:id="@+id/btn_tab1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="구글맵"/>

        <Button
            android:id="@+id/btn_tab2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="내 위치"/>

        <Button
            android:id="@+id/btn_tab3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Tab 3"/>


    </LinearLayout>

</RelativeLayout>


fragment_02.xml

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

    <com.google.android.gms.maps.MapView
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.google.android.gms.maps.MapFragment"
        />

</RelativeLayout>



MainActivity.java

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    Context context;
    private Button tab1, tab2, tab3;
    private Fragment fragment = null;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(context, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

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

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.ACCESS_FINE_LOCATION,
                            android.Manifest.permission.ACCESS_COARSE_LOCATION
                            //android.Manifest.permission.READ_EXTERNAL_STORAGE,
                            //android.Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
                    })
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        tab1 = findViewById(R.id.btn_tab1);
        tab2 = findViewById(R.id.btn_tab2);
        tab3 = findViewById(R.id.btn_tab3);

        tab1.setOnClickListener(this);
        tab2.setOnClickListener(this);
        tab3.setOnClickListener(this);

        if(findViewById(R.id.fragment_container) != null){
            Fragment01 fragment01 = new Fragment01();
            fragment01.setArguments(getIntent().getExtras());

            getFragmentManager().beginTransaction().add(R.id.fragment_container, fragment01).commitAllowingStateLoss();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_tab1:
                fragment = new Fragment01();
                selectFragment(fragment);
                break;
            case R.id.btn_tab2:
                fragment = new Fragment02();
                selectFragment(fragment);
                break;
            case R.id.btn_tab3:
                fragment = new Fragment03();
                selectFragment(fragment);
                break;
        }
    }

    private void selectFragment(Fragment fragment) {
        // 액티비티 내의 프래그먼트를 관리하려면 FragmentManager를 사용해야 함.
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.fragment_container,fragment);
        // FragmentTransaction을 변경하고 나면, 반드시 commit()을 호출해야 변경 내용이 적용됨
        fragmentTransaction.commit();
    }
}


Fragment2.java

package com.link2me.android.googlemap;

import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
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.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class Fragment02 extends Fragment implements OnMapReadyCallback {

    private FragmentActivity mContext;

    private static final String TAG = Fragment02.class.getSimpleName();
    private GoogleMap mMap;
    private MapView mapView = null;
    private Marker currentMarker = null;

    // The entry point to the Fused Location Provider.
    private FusedLocationProviderClient mFusedLocationProviderClient; // Deprecated된 FusedLocationApi를 대체
    private LocationRequest locationRequest;
    private Location mCurrentLocatiion;

    private final LatLng mDefaultLocation = new LatLng(37.56, 126.97);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    private static final int GPS_ENABLE_REQUEST_CODE = 2001;
    private static final int UPDATE_INTERVAL_MS = 1000 * 60 * 1;  // 1분 단위 시간 갱신
    private static final int FASTEST_UPDATE_INTERVAL_MS = 1000 * 30 ; // 30초 단위로 화면 갱신

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    public Fragment02() {
    }

    @Override
    public void onAttach(Activity activity) { // Fragment 가 Activity에 attach 될 때 호출된다.
        mContext =(FragmentActivity) activity;
        super.onAttach(activity);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 초기화 해야 하는 리소스들을 여기서 초기화 해준다.
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        // Layout 을 inflate 하는 곳이다.
        if (savedInstanceState != null) {
            mCurrentLocatiion = savedInstanceState.getParcelable(KEY_LOCATION);
            CameraPosition mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        View layout =  inflater.inflate(R.layout.fragment_02,container,false);
        mapView = (MapView)layout.findViewById(R.id.map);
        if(mapView != null) {
            mapView.onCreate(savedInstanceState);
        }
        mapView.getMapAsync(this);
        return layout;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        // Fragement에서의 OnCreateView를 마치고, Activity에서 onCreate()가 호출되고 나서 호출되는 메소드이다.
        // Activity와 Fragment의 뷰가 모두 생성된 상태로, View를 변경하는 작업이 가능한 단계다.
        super.onActivityCreated(savedInstanceState);

        //액티비티가 처음 생성될 때 실행되는 함수
        MapsInitializer.initialize(mContext);

        locationRequest = new LocationRequest()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) // 정확도를 최우선적으로 고려
                .setInterval(UPDATE_INTERVAL_MS) // 위치가 Update 되는 주기
                .setFastestInterval(FASTEST_UPDATE_INTERVAL_MS); // 위치 획득후 업데이트되는 주기

        LocationSettingsRequest.Builder builder =
                new LocationSettingsRequest.Builder();

        builder.addLocationRequest(locationRequest);

        // FusedLocationProviderClient 객체 생성
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(mContext);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        setDefaultLocation(); // GPS를 찾지 못하는 장소에 있을 경우 지도의 초기 위치가 필요함.

        getLocationPermission();

        updateLocationUI();

        getDeviceLocation();
    }

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mCurrentLocatiion = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    private void setDefaultLocation() {
        if (currentMarker != null) currentMarker.remove();

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(mDefaultLocation);
        markerOptions.title("위치정보 가져올 수 없음");
        markerOptions.snippet("위치 퍼미션과 GPS 활성 여부 확인하세요");
        markerOptions.draggable(true);
        markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(mDefaultLocation, 15);
        mMap.moveCamera(cameraUpdate);
    }

    String getCurrentAddress(LatLng latlng) {
        // 위치 정보와 지역으로부터 주소 문자열을 구한다.
        List<Address> addressList = null ;
        Geocoder geocoder = new Geocoder( mContext, Locale.getDefault());

        // 지오코더를 이용하여 주소 리스트를 구한다.
        try {
            addressList = geocoder.getFromLocation(latlng.latitude,latlng.longitude,1);
        } catch (IOException e) {
            Toast. makeText( mContext, "위치로부터 주소를 인식할 수 없습니다. 네트워크가 연결되어 있는지 확인해 주세요.", Toast.LENGTH_SHORT ).show();
            e.printStackTrace();
            return "주소 인식 불가" ;
        }

        if (addressList.size() < 1) { // 주소 리스트가 비어있는지 비어 있으면
            return "해당 위치에 주소 없음" ;
        }

        // 주소를 담는 문자열을 생성하고 리턴
        Address address = addressList.get(0);
        StringBuilder addressStringBuilder = new StringBuilder();
        for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
            addressStringBuilder.append(address.getAddressLine(i));
            if (i < address.getMaxAddressLineIndex())
                addressStringBuilder.append("\n");
        }

        return addressStringBuilder.toString();
    }

    LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);

            List<Location> locationList = locationResult.getLocations();

            if (locationList.size() > 0) {
                Location location = locationList.get(locationList.size() - 1);

                LatLng currentPosition
                        = new LatLng(location.getLatitude(), location.getLongitude());

                String markerTitle = getCurrentAddress(currentPosition);
                String markerSnippet = "위도:" + String.valueOf(location.getLatitude())
                        + " 경도:" + String.valueOf(location.getLongitude());

                Log.d(TAG, "Time :" + CurrentTime() + " onLocationResult : " + markerSnippet);

                //현재 위치에 마커 생성하고 이동
                setCurrentLocation(location, markerTitle, markerSnippet);
                mCurrentLocatiion = location;
            }
        }

    };

    private String CurrentTime(){
        Date today = new Date();
        SimpleDateFormat date = new SimpleDateFormat("yyyy/MM/dd");
        SimpleDateFormat time = new SimpleDateFormat("hh:mm:ss a");
        return time.format(today);
    }

    public void setCurrentLocation(Location location, String markerTitle, String markerSnippet) {
        if (currentMarker != null) currentMarker.remove();

        LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(currentLatLng);
        markerOptions.title(markerTitle);
        markerOptions.snippet(markerSnippet);
        markerOptions.draggable(true);

        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng);
        mMap.moveCamera(cameraUpdate);
    }

    private void getDeviceLocation() {
        try {
            if (mLocationPermissionGranted) {
                mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    private void getLocationPermission() {
        if (ContextCompat.checkSelfPermission(mContext,
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(mContext,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }


    public boolean checkLocationServicesStatus() {
        LocationManager locationManager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);

        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
                locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    }

    @Override
    public void onStart() { // 유저에게 Fragment가 보이도록 해준다.
        super.onStart();
        mapView.onStart();
        Log.d(TAG, "onStart ");
    }

    @Override
    public void onStop() {
        super.onStop();
        mapView.onStop();
        if (mFusedLocationProviderClient != null) {
            Log.d(TAG, "onStop : removeLocationUpdates");
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }

    @Override
    public void onResume() { // 유저에게 Fragment가 보여지고, 유저와 상호작용이 가능하게 되는 부분
        super.onResume();
        mapView.onResume();
        if (mLocationPermissionGranted) {
            Log.d(TAG, "onResume : requestLocationUpdates");
            mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, null);
            if (mMap!=null)
                mMap.setMyLocationEnabled(true);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mapView.onPause();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }

    @Override
    public void onDestroyView() { // 프래그먼트와 관련된 View 가 제거되는 단계
        super.onDestroyView();
        if (mFusedLocationProviderClient != null) {
            Log.d(TAG, "onDestroyView : removeLocationUpdates");
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }

    @Override
    public void onDestroy() {
        // Destroy 할 때는, 반대로 OnDestroyView에서 View를 제거하고, OnDestroy()를 호출한다.
        super.onDestroy();
        mapView.onDestroy();
    }

}


테스트한 소스 코드는 다른 기능을 좀 더 구현 테스트해보고 올려둘 생각이다.


728x90
블로그 이미지

Link2Me

,
728x90

구글 제공 구글맵 샘플코드는 https://github.com/googlemaps/android-samples/tree/master 에서 다운로드 받아서 필요한 것을 구현하면 된다.


지도 객체

- 하나의 지도는 View 요소와 인터페이스 요소로 구성된다.

- View 요소는 화면에 지도를 나타내는 역할을 하며, MapFragment 와 MapView 가 여기에 해당된다.

- 지도 화면은 Fragment 및 View로 제공된다. 이 중 한가지를 Layout 에 추가하면 화면에 지도가 나타난다.

- 지도를 화면에 나타내는 방법 중 가장 권장되는 것은 MapFragment 를 사용하는 것이다.

- Fragment 를 사용하지 않고 View를 바로 사용할 수도 있다.

  MapView를 Layout에 추가하면 MapFragment 를 사용한 것과 마찬가지로 지도가 화면에 나타난다.

- MapView를 사용할 때는 반드시 MapView가 포함된 액티비티의 라이프 사이클에 맞추어 onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy(), onSaveInstanceState(), onLowMemory()를 호출해야 합니다. 단, MapView가 프래그먼트에 포함될 경우 프래그먼트의 onCreateView() 또는 onViewCreated()에서 onCreate()를, onDestroyView()에서 onDestroy()를 호출해야 합니다. 그렇지 않으면 지도가 정상적으로 동작하지 않는다.
MapFragment를 사용하면 이러한 절차가 필요하지 않으므로 MapFragment를 사용하는 것을 권장한다.



FusedLocationProviderClient

https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient 참조

private FusedLocationProviderClient mFusedLocationProviderClient;

// FusedLocationProviderClient 객체 생성
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(mContext);



현재 위치를 획득하는 걸 LocationManager service = (LocationManager) getSystemService(Context.LOCATION_SERVICE); 를 활용해볼까 하고 좀 찾아봤더니 이건 오래된 방식이라고 나온다.


GPS 수신이 불가능한 곳에 위치할 경우에는 네트워크 사업자의 위치정보를 받아서 처리하는 걸 추가하려고 하는데 아직 여기까지는 처리하지 못했다.


https://webnautes.tistory.com/1249 에 나온 예제가 너무 잘 되어 있어 거의 활용하면서 구글맵 예제와 비교하면서 수정해서 테스트 해봤다.


activity_current_place.xml

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">

            <ImageView
                android:id="@+id/navibar_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:adjustViewBounds="false"
                android:scaleType="fitXY"
                android:src="@drawable/navibar_bg" />

            <TextView
                android:id="@+id/navibar_text"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:singleLine="true"
                android:text="No Title"
                android:textColor="@android:color/white"
                android:textSize="22dp" />

            <Button
                android:id="@+id/list_btn"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:background="@drawable/btn_playlist" />

            <Button
                android:id="@+id/home_btn"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:background="@drawable/btn_home_c" />

        </RelativeLayout>

        <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/realtimemap"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>

</LinearLayout>


CurrentPlace.java

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class CurrentPlace extends AppCompatActivity implements View.OnClickListener, OnMapReadyCallback {
    private static final String TAG = CurrentPlace.class.getSimpleName();
    Context mContext;
    TextView title;

    private GoogleMap mMap;
    private Marker currentMarker = null;

    // The entry point to the Fused Location Provider.
    private FusedLocationProviderClient mFusedLocationProviderClient; // Deprecated된 FusedLocationApi를 대체
    private LocationRequest locationRequest;
    private Location mCurrentLocatiion;

    private final LatLng mDefaultLocation = new LatLng(37.56, 126.97);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    private static final int GPS_ENABLE_REQUEST_CODE = 2001;
    private static final int UPDATE_INTERVAL_MS = 1000 * 60 * 15;  // LOG 찍어보니 이걸 주기로 하지 않는듯
    private static final int FASTEST_UPDATE_INTERVAL_MS = 1000 * 30 ; // 30초 단위로 화면 갱신

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocatiion = savedInstanceState.getParcelable(KEY_LOCATION);
            CameraPosition mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        setContentView(R.layout.activity_current_place);
        mContext = CurrentPlace.this;

        // button action...
        findViewById(R.id.list_btn).setOnClickListener(this);
        findViewById(R.id.home_btn).setOnClickListener(this);

        // title set
        title = (TextView) this.findViewById(R.id.navibar_text); // title
        title.setText("실시간 위치 표시");

        locationRequest = new LocationRequest()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) // 정확도를 최우선적으로 고려
                .setInterval(UPDATE_INTERVAL_MS) // 위치가 Update 되는 주기
                .setFastestInterval(FASTEST_UPDATE_INTERVAL_MS); // 위치 획득후 업데이트되는 주기

        LocationSettingsRequest.Builder builder =
                new LocationSettingsRequest.Builder();

        builder.addLocationRequest(locationRequest);

        // Construct a FusedLocationProviderClient.
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);

        // Build the map.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.realtimemap);
        mapFragment.getMapAsync(this);
    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mCurrentLocatiion);
            super.onSaveInstanceState(outState);
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.list_btn:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(mContext, view);
                Menu menu = popupMenu.getMenu();
                menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "Get Place");
                //menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.option_get_place:
                                //showCurrentPlace(); // 아직 이 부분 미 구현
                                break;
                        }
                        return true;
                    }
                });
                popupMenu.show();
                break;
            case R.id.home_btn:
                Intent intent = new Intent(CurrentPlace.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            default:
                break;
        }
    }

    @Override
    public void onMapReady(GoogleMap map) {
        Log.e(TAG, "onMapReady :");

        mMap = map;

        setDefaultLocation(); // GPS를 찾지 못하는 장소에 있을 경우 지도의 초기 위치가 필요함.

        getLocationPermission();

        updateLocationUI();

        getDeviceLocation();
    }

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mCurrentLocatiion = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    private void setDefaultLocation() {
        if (currentMarker != null) currentMarker.remove();

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(mDefaultLocation);
        markerOptions.title("위치정보 가져올 수 없음");
        markerOptions.snippet("위치 퍼미션과 GPS 활성 여부 확인하세요");
        markerOptions.draggable(true);
        markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(mDefaultLocation, 15);
        mMap.moveCamera(cameraUpdate);
    }

    String getCurrentAddress(LatLng latlng) {
        // 위치 정보와 지역으로부터 주소 문자열을 구한다.
        List<Address> addressList = null ;
        Geocoder geocoder = new Geocoder( this, Locale.getDefault());

        // 지오코더를 이용하여 주소 리스트를 구한다.
        try {
            addressList = geocoder.getFromLocation(latlng.latitude,latlng.longitude,1);
        } catch (IOException e) {
            Toast. makeText( this, "위치로부터 주소를 인식할 수 없습니다. 네트워크가 연결되어 있는지 확인해 주세요.", Toast.LENGTH_SHORT ).show();
            e.printStackTrace();
            return "주소 인식 불가" ;
        }

        if (addressList.size() < 1) { // 주소 리스트가 비어있는지 비어 있으면
            return "해당 위치에 주소 없음" ;
        }

        // 주소를 담는 문자열을 생성하고 리턴
        Address address = addressList.get(0);
        StringBuilder addressStringBuilder = new StringBuilder();
        for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
            addressStringBuilder.append(address.getAddressLine(i));
            if (i < address.getMaxAddressLineIndex())
                addressStringBuilder.append("\n");
        }

        return addressStringBuilder.toString();
    }

    LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);

            List<Location> locationList = locationResult.getLocations();

            if (locationList.size() > 0) {
                Location location = locationList.get(locationList.size() - 1);

                LatLng currentPosition
                        = new LatLng(location.getLatitude(), location.getLongitude());

                String markerTitle = getCurrentAddress(currentPosition);
                String markerSnippet = "위도:" + String.valueOf(location.getLatitude())
                        + " 경도:" + String.valueOf(location.getLongitude());

                Log.d(TAG, "Time :" + CurrentTime() + " onLocationResult : " + markerSnippet);

                // Update 주기를 확인해보려고 시간을 찍어보았음.
                //현재 위치에 마커 생성하고 이동
                setCurrentLocation(location, markerTitle, markerSnippet);
                mCurrentLocatiion = location;
            }
        }

    };

    private String CurrentTime(){
        Date today = new Date();
        SimpleDateFormat date = new SimpleDateFormat("yyyy/MM/dd");
        SimpleDateFormat time = new SimpleDateFormat("hh:mm:ss a");
        return time.format(today);
    }

    public void setCurrentLocation(Location location, String markerTitle, String markerSnippet) {
        if (currentMarker != null) currentMarker.remove();

        LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(currentLatLng);
        markerOptions.title(markerTitle);
        markerOptions.snippet(markerSnippet);
        markerOptions.draggable(true);

        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng);
        mMap.moveCamera(cameraUpdate);
    }

    private void getDeviceLocation() {
        try {
            if (mLocationPermissionGranted) {
                mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    private void getLocationPermission() {
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mLocationPermissionGranted) {
            Log.d(TAG, "onStart : requestLocationUpdates");
            mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, null);
            if (mMap!=null)
                mMap.setMyLocationEnabled(true);
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mFusedLocationProviderClient != null) {
            Log.d(TAG, "onStop : removeLocationUpdates");
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mFusedLocationProviderClient != null) {
            Log.d(TAG, "onDestroy : removeLocationUpdates");
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }
}


앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.link2me.android.googlemap"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        // multiDexEnabled true // minSdkVersion 이 21 이상인 경우 사용
    }

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

}

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

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    //implementation 'com.google.android.gms:play-services-places:16.0.0' // OLD
    //implementation 'com.google.android.libraries.places:places:2.0.0'  // NEW
    implementation 'com.google.android.libraries.places:places-compat:2.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    implementation 'androidx.multidex:multidex:2.0.0' // minSdkVersion 이 21 이하인 경우 사용
    implementation 'com.google.android.material:material:1.2.0'
}


최근에는 minSdkVersion 을 23 이상으로 하라는 코드가 상당히 많다.

보안이 강화되면서 minSdkVersion 버전을 올리는 것이 좋은 듯하다.

728x90
블로그 이미지

Link2Me

,
728x90

구글맵에 현재 위치 표시하기


Activity 에서 현재 위치를 표시해주는 것은 구글맵에 나온 예제를 그대로 실행하면 잘 된다.

Fragment 에서 실행하려고 시도했는데 에러가 발생한다.

Fragment 에 대한 이해 부족인 거 같아서 다른 예제를 좀 찾아서 시도해봐야 할 거 같다.


https://developers.google.com/maps/documentation/android-sdk/current-place-tutorial


AndroidManifext.xml 파일 내용은 https://link2me.tistory.com/1700 하단 내용 참조하면 된다.


activity_current_place.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"
    tools:context=".MapsActivityCurrentPlace">

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


custom_info_contents.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layoutDirection="locale"
    android:orientation="vertical">
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textColor="#ff000000"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/snippet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#ff7f7f7f" />
</LinearLayout>


current_place_menu.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">

    <item
        android:id="@+id/option_get_place"
        android:title="Get Place"
        app:showAsAction="always" />
</menu>


the options menu 를 사용하지 않고 별도의 타이틀바에서 처리되도록 만들기 위해서 코드를 아래 코든는 삭제했다.

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.current_place_menu, menu);
        return true;
    }
 

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }


위 코드 대신에 추가한 코드는

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.list_btn:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(mContext, view);
                Menu menu = popupMenu.getMenu();
                menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "Get Place");
                //menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.option_get_place:
                                showCurrentPlace();
                                break;
                        }
                        return true;
                    }
                });
                popupMenu.show();
                break;
            case R.id.home_btn:
                Intent intent = new Intent(MapsActivityCurrentPlace.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            default:
                break;
        }
    }




MapsActivityCurrentPlace.java

구글 맵 예제로 제공하는 사항이 코드를 구현해보면 The Google Play Services Places SDK is deprecated. 라고 나온다.

찾아보니 2019.7.29일 서비스가 종료되었단다.

해결방안은 앱 build.gradle 에서

//implementation 'com.google.android.gms:play-services-places:16.0.0' // OLD
implementation 'com.google.android.libraries.places:places-compat:2.0.0'

를 해주면 된다고 나와 컴파일 해보니 잘 된다. 아직은 OLD 로 해도 잘 되더라.


package com.link2me.android.googlemap;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.location.places.PlaceDetectionClient;
import com.google.android.gms.location.places.PlaceLikelihood;
import com.google.android.gms.location.places.PlaceLikelihoodBufferResponse;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

public class MapsActivityCurrentPlace extends AppCompatActivity implements View.OnClickListener, OnMapReadyCallback {
    // 현재 위치를 구글 지도에 표시하는 예제
    // https://developers.google.com/maps/documentation/android-sdk/current-place-tutorial 참조
    private static final String TAG = MapsActivityCurrentPlace.class.getSimpleName();
    Context mContext;
    TextView title;

    private GoogleMap mMap;
    private CameraPosition mCameraPosition;

    // The entry points to the Places API.
    private GeoDataClient mGeoDataClient;
    private PlaceDetectionClient mPlaceDetectionClient;

    // The entry point to the Fused Location Provider.
    private FusedLocationProviderClient mFusedLocationProviderClient;

    // A default location (Sydney, Australia) and default zoom to use when location permission is not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private Location mLastKnownLocation;

    // Keys for storing activity state.
    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    // Used for selecting the current place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] mLikelyPlaceNames;
    private String[] mLikelyPlaceAddresses;
    private String[] mLikelyPlaceAttributions;
    private LatLng[] mLikelyPlaceLatLngs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Retrieve location and camera position from saved instance state.
        if (savedInstanceState != null) {
            mLastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        setContentView(R.layout.activity_maps_current_place);
        mContext = MapsActivityCurrentPlace.this;

        // button action...
        findViewById(R.id.list_btn).setOnClickListener(this);
        findViewById(R.id.home_btn).setOnClickListener(this);

        // title set
        title = (TextView) this.findViewById(R.id.navibar_text); // title
        title.setText(getIntent().getExtras().getString("title"));

        // Construct a GeoDataClient.
        mGeoDataClient = Places.getGeoDataClient(this, null);

        // Construct a PlaceDetectionClient.
        mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

        // Construct a FusedLocationProviderClient.
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);

        // Build the map.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }

    /**
     * Manipulates the map when it's available.
     * This callback is triggered when the map is ready to be used.
     */
    @Override
    public void onMapReady(GoogleMap map) {
        mMap = map;

        // Use a custom info window adapter to handle multiple lines of text in the info window contents.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {

            @Override
            // Return null here, so that getInfoContents() is called next.
            public View getInfoWindow(Marker arg0) {
                return null;
            }

            @Override
            public View getInfoContents(Marker marker) {
                // Inflate the layouts for the info window, title and snippet.
                View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents,
                        (FrameLayout) findViewById(R.id.map), false);

                TextView title = ((TextView) infoWindow.findViewById(R.id.title));
                title.setText(marker.getTitle());

                TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
                snippet.setText(marker.getSnippet());

                return infoWindow;
            }
        });

        // Prompt the user for permission.
        getLocationPermission();

        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();

        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }

    /**
     * Gets the current location of the device, and positions the map's camera.
     */
    private void getDeviceLocation() {
        /*
         * Get the best and most recent location of the device, which may be null in rare
         * cases when a location is not available.
         */
        try {
            if (mLocationPermissionGranted) {
                Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful()) {
                            // Set the map's camera position to the current location of the device.
                            mLastKnownLocation = task.getResult();
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(mLastKnownLocation.getLatitude(),
                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            mMap.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                            mMap.getUiSettings().setMyLocationButtonEnabled(false);
                        }
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }


    /**
     * Prompts the user for permission to use the device location.
     */
    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    /**
     * Handles the result of the request for location permissions.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }

    /**
     * Prompts the user to select the current place from a list of likely places, and shows the
     * current place on the map - provided the user has granted location permission.
     */
    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            // Get the likely places - that is, the businesses and other points of interest that
            // are the best match for the device's current location.
            @SuppressWarnings("MissingPermission") final
            Task<PlaceLikelihoodBufferResponse> placeResult =
                    mPlaceDetectionClient.getCurrentPlace(null);
            placeResult.addOnCompleteListener
                    (new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
                        @Override
                        public void onComplete(@NonNull Task<PlaceLikelihoodBufferResponse> task) {
                            if (task.isSuccessful() && task.getResult() != null) {
                                PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();

                                // Set the count, handling cases where less than 5 entries are returned.
                                int count;
                                if (likelyPlaces.getCount() < M_MAX_ENTRIES) {
                                    count = likelyPlaces.getCount();
                                } else {
                                    count = M_MAX_ENTRIES;
                                }

                                int i = 0;
                                mLikelyPlaceNames = new String[count];
                                mLikelyPlaceAddresses = new String[count];
                                mLikelyPlaceAttributions = new String[count];
                                mLikelyPlaceLatLngs = new LatLng[count];

                                for (PlaceLikelihood placeLikelihood : likelyPlaces) {
                                    // Build a list of likely places to show the user.
                                    mLikelyPlaceNames[i] = (String) placeLikelihood.getPlace().getName();
                                    mLikelyPlaceAddresses[i] = (String) placeLikelihood.getPlace()
                                            .getAddress();
                                    mLikelyPlaceAttributions[i] = (String) placeLikelihood.getPlace()
                                            .getAttributions();
                                    mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();

                                    i++;
                                    if (i > (count - 1)) {
                                        break;
                                    }
                                }

                                // Release the place likelihood buffer, to avoid memory leaks.
                                likelyPlaces.release();

                                // Show a dialog offering the user the list of likely places, and add a
                                // marker at the selected place.
                                openPlacesDialog();

                            } else {
                                Log.e(TAG, "Exception: %s", task.getException());
                            }
                        }
                    });
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");

            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title("기본 위치")
                    .position(mDefaultLocation)
                    .snippet("No places found, because location is disabled."));

            // Prompt the user for permission.
            getLocationPermission();
        }
    }

    /**
     * Displays a form allowing the user to select a place from a list of likely places.
     */
    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }

                // Add a marker for the selected place, with an info window showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));

                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };

        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle("장소를 선택하세요.")
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }

    /**
     * Updates the map's UI settings based on whether the user has granted location permission.
     */
    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.list_btn:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(mContext, view);
                Menu menu = popupMenu.getMenu();
                menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                //menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.option_get_place:
                                showCurrentPlace();
                                break;
                        }
                        return true;
                    }
                });
                popupMenu.show();
                break;
            case R.id.home_btn:
                Intent intent = new Intent(MapsActivityCurrentPlace.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            default:
                break;
        }
    }
}



728x90
블로그 이미지

Link2Me

,
728x90

코드를 구현하다보니 파싱처리를 하는데 동일한 단어가 들어간 곳이 다른 곳에도 존재하는 걸 몰랐다.

파싱할 내용이 다르다보니 에러가 발생했다.


해결법은

try {
    int stlocation = str.indexOf("GW=");
    int endlocation = str.length();
    String temp = str.substring(stlocation + 4, endlocation).trim();
    String[] part = temp.split("\\s{1,}"); // 공백 단위로 메시지를 분리하라.
    if (part[3].length() > 4) {
        ipaddr = part[3];
    }
} catch(IndexOutOfBoundsException e) {
    System.out.println(e);
}


와 같이 try catch 문으로 해결했다.



728x90
블로그 이미지

Link2Me

,
728x90

https://developers.google.com/maps/documentation/android-sdk/get-api-key 를 참조한다.


구글 맵을 사용하기 위해서는 먼저 Google API Key 를 발급받아야 한다.


1. https://console.developers.google.com/ 에 접속한다.


2. 프로젝트를 신규로 만들거나 이미 만들어진 프로젝트를 선택한다.

   - 이미 만들어진 프로젝트 Contacts 를 선택했다.

     프로젝트를 연습한다고 신규로 계속 만들었는데 그럴 필요가 없다.


3. 왼쪽 메뉴에서 라이브러리를 선택한다.

   - Maps SDK for Android 를 선택한다.

   - 사용설정을 누른다.


4. 사용자 인증정보 만들기

    - 구글 Firebase Message 를 사용하기 위해 등록하면서 만들어진 사용자 인증키가 있더라.


   - 내용을 확인해보니 Server key 가 Firebase Cloud Message 에서 사용하는 키(이전 서버키)더라.

   - Android key 를 눌러서 선택했다.

    - 항목 추가를 누른다.

① 패키지 이름은 B를 복사하여 붙여넣기 한다.



② SHA-1 인증서 디지털 지문

     - A를 눌러서 복사한다.

     - Android Studio 가 설치된 경로에 있는 jre/bin 폴더에 keytool.exe 파일이 존재한다.

     - 이 폴더에서 cmd 창 상태로 전환해서 A 복사한 걸 붙여넣기 하고 엔터키를 친다.

     - SHA-1 키를 복사하여 붙여넣기 한다.

       만약 Drag 가 안된다면 파일로 저장해서 Copy & Paste 하면 된다.

       회사 컴과 집 컴이 각각 다르다면 SHA-1 이 다를 것이므로 항목 추가로 각각 등록해서 테스트하면 된다.

      - 패키지 이름과 SHA-1 인증서 디지털 지문을 복사하여 붙여넣기를 한 다음 완료를 누른다

      - 저장을 누르면 Key 발급 준비가 된 것이다.


이제 바로 위의 A를 눌러서 복사한 API 키를

AndroidManifest.xml 파일에 구글 API Key를 등록한다.

키는 임의로 변경한 것이 있으므로 실제 키와는 다르다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.link2me.android.googlemap">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><!-- GPS -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><!-- WiFi or mobile -->

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MapsActivityCurrentPlace"></activity>

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="AIzaSyAgfzQZqx7nFVFhwKAhviwRP5Rmi9bmZPE" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


앱 build.gradle

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

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation 'com.google.android.gms:play-services-places:16.0.0'
    implementation 'com.google.android.libraries.places:places:2.0.0'

}
 


이제 구글맵을 사용할 준비는 끝났다.

728x90
블로그 이미지

Link2Me

,
728x90

자바(Java) 음력 일자 변환 라이브러리 설치법은 https://link2me.tistory.com/1695 를 참조하면 된다.

음력에 대한 자세한 내용을 알고 싶다면 http://oops.org/project/manse/https://github.com/OOPS-ORG-PHP/Lunar 를 읽어보면 도움이 될 것이다. 고영창 씨께서 고생해서 자료와 알고리즘을 만들었던 거 같다.

음력 앱, 프로그램 등을 테스트하다보면 음력일자가 약간 틀리게 나오는 경우도 있다. 정확한 데이터를 기준으로 해야 하는데, 데이터의 기준이 이것이 정확하다고 하는 걸 찾는게 어려운 거 같다.

음력 데이터를 MySQL 버전으로 DB화를 해 놓은 걸 SQLite 로 포팅하는 걸 시도해봐야 하나 고민되기도 한다.


자바(Java)에서 사용하는 라이브러리가 맞다는 가정하에 이 코드를 기준으로 음력과 양력을 변환하여 법정공휴일 처리를 하도록 했다. 음력을 지원하는 앱을 실행해보면서 2019년도 추석, 설 데이터, 어린이날 대체공휴일 등이 맞는 걸로 봐서 음력 데이터가 정확한 거 같아서 아래 코드를 노가다 수준으로 구현해서 테스트 했다.


법정공휴일 처리를 위해서 Holidays Class 를 만들었다.

ArrayList를 사용해서 입력 연도에 해당되는 법정공휴일 처리를 하려고 한다.

public class Holidays implements Comparable<Holidays>{
    // ArrayList의 type이 Comparable을 implements한 경우에만 sort 메소드의 정렬 기능을 사용할 수 있다
    private String year; // 연도
    private String date; // 월일
    private String name; // 휴일 명칭

    public Holidays() {
    }

    public Holidays(String year, String date, String name) {
        this.year = year;
        this.date = date;
        this.name = name;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public int compareTo(Holidays o) {
        return this.date.compareTo(o.date);
    }
}


이제 법정공휴일 양력과 음력을 처리할 ArrayList<Holidays> holidayArray(String yyyy) 메소드를 만들었다.

ArrayList return 메소드 만드는 예제는 https://link2me.tistory.com/1698 를 참조하면 된다.

사용법은 ArrayList<Holidays> arr = LunarCalendar.holidayArray("2019"); 로 결과를 얻어서 처리하면 된다.

import com.ibm.icu.util.ChineseCalendar;
import com.tistory.link2me.item.Holidays;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;

public class LunarCalendar {
    static ArrayList<Holidays> holidaysArrayList = new ArrayList<>();

    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    public static String Lunar2Solar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));

        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }

    /**
     * 양력날짜를 음력날짜로 변환
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력날짜 (yyyyMMdd)
     */
    public static String Solar2Lunar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim() ;
        if( date.length() != 8 ) {
            if( date.length() == 4 )
                date = date + "0101" ;
            else if( date.length() == 6 )
                date = date + "01" ;
            else if( date.length() > 8 )
                date = date.substring(0,8) ;
            else
                return "" ;
        }

        cal.set( Calendar.YEAR, Integer.parseInt(date.substring(0,4)) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(date.substring(4,6))-1 ) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)) ) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString() ;
    }


    public static ArrayList<Holidays> holidayArray(String yyyy){
        holidaysArrayList.clear(); // 데이터 초기화
        // 양력 휴일
        addHolidaysItem(yyyy,"0101" ,"신정");
        addHolidaysItem(yyyy,"0301" ,"삼일절");
        addHolidaysItem(yyyy,"0505" ,"어린이날");
        addHolidaysItem(yyyy,"0606" ,"현충일" );
        addHolidaysItem(yyyy,"0815" ,"광복절");
        addHolidaysItem(yyyy,"1003" ,"개천절");
        addHolidaysItem(yyyy,"1009" ,"한글날");
        addHolidaysItem(yyyy,"1225" ,"성탄절");

        // 음력 휴일
        String prev_seol = String.valueOf(Integer.parseInt(Lunar2Solar(yyyy+"0101")) -1);
        addHolidaysItem(yyyy,prev_seol.substring(4) ,"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0101"),"설날");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0102"),"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0408"),"석탄일");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0814"),"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0815"),"추석");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0816"),"");

        try {
            // 어린이날 대체공휴일 검사 : 어린이날은 토요일, 일요일인 경우 그 다음 평일을 대체공유일로 지정
            int childDayChk = WeekendValue(yyyy+"0505");
            if(childDayChk == 1) addHolidaysItem(yyyy,"0506" ,"대체공휴일");
            if(childDayChk == 7) addHolidaysItem(yyyy,"0507" ,"대체공휴일");

            // 설날 대체공휴일 검사
            if(WeekendValue(Lunar2Solar(yyyy+"0101"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0101"))==2) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0102"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");

            // 추석 대체공휴일 검사
            if(WeekendValue(Lunar2Solar(yyyy+"0814"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0815"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0816"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
        } catch (ParseException e) {
            e.printStackTrace();
        }

        Collections.sort(holidaysArrayList); // 오름차순 정렬

        return holidaysArrayList;
    }

    private static String SolarDays(String yyyy, String date){
        return Lunar2Solar(yyyy+date).substring(4);
    }

    private static void addHolidaysItem(String year, String date, String name ){
        Holidays item = new Holidays();
        item.setYear(year);
        item.setDate(date);
        item.setName(name);
        holidaysArrayList.add(item);
    }

    private static int WeekendValue(String date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar cal = Calendar.getInstance();
        cal.setTime(sdf.parse(date));
        return cal.get(Calendar.DAY_OF_WEEK);
        // Calendar.SUNDAY : 1
        // Calendar.SATURDAY : 7
    }

}


사용 샘플 예제

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.tistory.link2me.item.Holidays;
import com.tistory.link2me.util.Lunar2Solar;
import com.tistory.link2me.util.LunarCalendar;

import java.util.ArrayList;
import java.util.Calendar;

public class CalendarActivity extends AppCompatActivity {

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

        System.out.println("Calendar.SUNDAY : "+Calendar.SUNDAY);
        System.out.println("Calendar.SATURDAY : "+Calendar.SATURDAY);

        ArrayList<Holidays> arr = LunarCalendar.holidayArray("2019");
        for(int i=0; i < arr.size();i++){
            System.out.println(arr.get(i).getYear() +"년 " + arr.get(i).getDate() +", " + arr.get(i).getName() );
        }

    }
}


결과 Logcat 화면


728x90
블로그 이미지

Link2Me

,
728x90

ArrayList 를 리턴 받는 함수를 만드는 방법이다.


import java.util.ArrayList;

public class Test {
    public ArrayList<Integer> myNumbers() {
        ArrayList<Integer> numbers = new ArrayList<Integer>();
        numbers.add(3);
        numbers.add(5);
        numbers.add(8);
        numbers.add(11);
        return (numbers);

    }
}


public class Main {

    public static void main(String[] args) {
        Test test = new Test();
        ArrayList<Integer> arr = test.myNumbers();
    }

}

728x90

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

Convert HashMap To ArrayList  (0) 2019.10.21
[Java] Convert Month String to Integer  (0) 2019.10.14
자바 null 비교, String 비교, 정수 비교  (0) 2019.09.20
공백으로 문자열 분리  (0) 2019.09.11
Java 제네릭(Generic)  (0) 2019.08.27
블로그 이미지

Link2Me

,
728x90

[ 태음력의 조정 ]
    235회합월은 일수로는 6940일(days)이다(235 x 29.530588일 = 6939.68818일).
    6940일의 1/19은 6940일 / 19 = 365.263157894737일(365 + 1/4 + 1/76일)이다.
    365.263157894737일은 12회합월(synodic months)인
    12 x 29.530588일 = 354.367056일보다 10.896101894737일(365.263157894737 - 354.367056) 더 크다.
    이것이 뜻하는 바는 음력으로 1년이 지나면 태양력과는 약 11일의 차이가 난다는 것을 의미한다.
    따라서 음력으로 3년이 지나면 태양력과는 약 33일의 차이가 나게 된다.
    따라서 3년마다 음력에 1달을 추가하며 13개의 음력 달들이 있게 하면,
    태음력은 어느 정도 근사하게 태양력과 일치하게 된다.
    태양력 19년 동안에 이런 작업을 총 7번(19년/3년 = 6.33) 행하면 된다.
    이런 이유로, 메톤 주기를 19년 7윤법이라고도 하였다.
 
메톤은 19태양년을 6,940일로 보고,
이것을 큰달(30일) 125개월과 작은달(29일) 110개월의 총 235개월 6,940일로 배분하였다.
이 방법은 평년(12개월) 12년과 윤년(13개월) 7년의 총 235개월로
태음력 달력을 구성하는 것과 동일하므로 19년 7윤법이라고도 하였다.

메톤주기의 존재를 알 수 있는 예로,
특정한 양력 날짜와 그에 대응하는 음력 날짜는 19년이 지나면 다시 일치한다는 사실을 찾을 수 있다.

하지만 정확히 맞물리는 건 아니라서 하루 정도 어긋나기도 한다. 


음력 날짜는 어느 경도를 기준으로 했느냐에 따라 하루 정도 차이가 있어서 때려 맞춰야 한다는 얘기가 있다.

한국천문연구원에서 계산한 2050년까지 음양력 자료들이 수록된 만세력을 기준으로 음력 날짜를 맞추면 될 거 같다.

음력을 알아내는 일정한 규칙은 없다고 한다.

아래 변환 코드로 내 생년월일, 딸래미 생년월일을 양력 → 음력 변환을 했을 때는 정확하게 결과가 도출되었다.


검색한 걸 참고하면 도움될 거 같아서 링크를 걸어둔다.

http://lifesci.net/pod/bbs/board.php?bo_table=B01&wr_id=1917



PHP 달력 만들기 https://link2me.tistory.com/1545 코드에 있는 함수를 자바 코드로 변환을 했다.

자동 변환이 아니라 코드에 맞게 함수를 찾아서 수정했다.

음력이 지원되는 Custom 달력을 만들어 볼 목적으로 코드를 변환하여 테스트 해보는 중이다.

PHP 원본 코드와 Java 로 변환한 코드를 같이 첨부했다.

Java 변환 코드는 Android 에서 활용하기 위한 목적이라 Android Studio 에서 작성했다.


PHP 코드

lun2sol.php



Lunar2Solar.java

Lunar2Solar.java

Lunar2Solar.java


자바 음력/양력 변환 코드

public class Lunar2Solar {
    // 음력 데이터 (평달 - 작은달 :1,  큰달:2 )
    // (윤달이 있는 달 - 평달이 작고 윤달도 작으면 :3 , 평달이 작고 윤달이 크면 : 4)
    // (윤달이 있는 달 - 평달이 크고 윤달이 작으면 :5,  평달과 윤달이 모두 크면 : 6)
    final static String[] LunarInfo = new String[]
            {"1212122322121","1212121221220","1121121222120","2112132122122","2112112121220",
                "2121211212120","2212321121212","2122121121210","2122121212120","1232122121212",
                "1212121221220","1121123221222","1121121212220","1212112121220","2121231212121",
                "2221211212120","1221212121210","2123221212121","2121212212120","1211212232212",
                "1211212122210","2121121212220","1212132112212","2212112112210","2212211212120",
                "1221412121212","1212122121210","2112212122120","1231212122212","1211212122210",
                "2121123122122","2121121122120","2212112112120","2212231212112","2122121212120",
                "1212122121210","2132122122121","2112121222120","1211212322122","1211211221220",
                "2121121121220","2122132112122","1221212121120","2121221212110","2122321221212",
                "1121212212210","2112121221220","1231211221222","1211211212220","1221123121221",
                "2221121121210","2221212112120","1221241212112","1212212212120","1121212212210",
                "2114121212221","2112112122210","2211211412212","2211211212120","2212121121210",
                "2212214112121","2122122121120","1212122122120","1121412122122","1121121222120",
                "2112112122120","2231211212122","2121211212120","2212121321212","2122121121210",
                "2122121212120","1212142121212","1211221221220","1121121221220","2114112121222",
                "1212112121220","2121211232122","1221211212120","1221212121210","2121223212121",
                "2121212212120","1211212212210","2121321212221","2121121212220","1212112112210",
                "2223211211221","2212211212120","1221212321212","1212122121210","2112212122120",
                "1211232122212","1211212122210","2121121122210","2212312112212","2212112112120",
                "2212121232112","2122121212110","2212122121210","2112124122121","2112121221220",
                "1211211221220","2121321122122","2121121121220","2122112112322","1221212112120",
                "1221221212110","2122123221212","1121212212210","2112121221220","1211231212222",
                "1211211212220","1221121121220","1223212112121","2221212112120","1221221232112",
                "1212212122120","1121212212210","2112132212221","2112112122210","2211211212210",
                "2221321121212","2212121121210","2212212112120","1232212122112","1212122122110",
                "2121212322122","1121121222120","2112112122120","2211231212122","2121211212120",
                "2122121121210","2124212112121","2122121212120","1212121223212","1211212221220",
                "1121121221220","2112132121222","1212112121220","2121211212120","2122321121212",
                "1221212121210","2121221212120","1232121221212","1211212212210","2121123212221",
                "2121121212220","1212112112220","1221231211221","2212211211220","1212212121210",
                "2123212212121","2112122122120","1211212322212","1211212122210","2121121122120",
                "2212114112122","2212112112120","2212121211210","2212232121211","2122122121210",
                "2112122122120","1231212122212","1211211221220","2121121321222","2121121121220",
                "2122112112120","2122141211212","1221221212110","2121221221210","2114121221221"};

    final static int[] arrayLDAY = new int[]{31,0,31,30,31,30,31,31,30,31,30,31};

    public static String lun2sol(String yyyymmdd) {
        int YunMonthFlag = 0;
        int gf_lun2sol = 0;
        int gf_yun = 0;
        int leap = 0;
        int syear = 0;
        int smonth = 0;
        int sday = 0;

        int getYEAR = Integer.parseInt(yyyymmdd.substring(0,4));
        int getMONTH = Integer.parseInt(yyyymmdd.substring(4,6));
        int getDAY = Integer.parseInt(yyyymmdd.substring(6,8));

        String arrayYUKSTR="甲-乙-丙-丁-戊-己-庚-辛-壬-癸";
        String[] arrayYUK = arrayYUKSTR.split("-");

        String arrayGAPSTR="子-丑-寅-卯-辰-巳-午-未-申-酉-戌-亥";
        String[] arrayGAP = arrayGAPSTR.split("-");

        String arrayDDISTR="쥐-소-범-토끼-용-뱀-말-양-원숭이-닭-개-돼지";
        String[] arrayDDI = arrayDDISTR.split("-");

        String arrayWEEKSTR="일-월-화-수-목-금-토";
        String[] arrayWEEK = arrayWEEKSTR.split("-");

        if (getYEAR <= 1881 || getYEAR >= 2050) { //년수가 해당일자를 넘는 경우
            YunMonthFlag = 0;
        }

        if (getMONTH > 12) { // 달수가 13이 넘는 경우
            YunMonthFlag = 0;
        }

        int m1 = getYEAR - 1881;
        if (Integer.parseInt(LunarInfo[m1].substring(12,13)) == 0) { // 윤달이 없는 해임
            YunMonthFlag = 0;
        } else {
            if (Integer.parseInt(LunarInfo[m1].substring(getMONTH,getMONTH+1)) > 2) {
                YunMonthFlag = 1;
            } else {
                YunMonthFlag = 0;
            }
        }

        m1 = -1;
        int td = 0;

        if (getYEAR > 1881 && getYEAR < 2050) {
            m1 = getYEAR - 1882;
            for (int i=0; i <= m1; i++) {
                for (int j=0; j <= 12; j++) {
                    td = td + Integer.parseInt(LunarInfo[i].substring(j,j+1));
                }
                if (Integer.parseInt(LunarInfo[i].substring(12,13)) == 0) {
                    td = td + 336;
                } else {
                    td = td + 362;
                }
            }
        } else {
            gf_lun2sol = 0;
        }

        m1++;
        int n2 = getMONTH - 1;
        int m2 = -1;

        while(true) {
            m2++;
            if (Integer.parseInt(LunarInfo[m1].substring(m2,m2+1)) > 2) {
                td = td + 26 + Integer.parseInt(LunarInfo[m1].substring(m2,m2+1));
                n2++;
            } else {
                if (m2 == n2) {
                    if (gf_yun == 1) {
                        td = td + 28 + Integer.parseInt(LunarInfo[m1].substring(m2,m2+1));
                    }
                    break;
                } else {
                    td = td + 28 + Integer.parseInt(LunarInfo[m1].substring(m2,m2+1));
                }
            }
        }

        td = td + getDAY + 29;
        m1 = 1880;
        while(true) {
            m1++;
            if (m1 % 400 == 0 || m1 % 100 != 0 && m1 % 4 == 0) {
                leap = 1;
            } else {
                leap = 0;
            }

            if (leap == 1) {
                m2 = 366;
            } else {
                m2 = 365;
            }

            if (td < m2) break;
            td = td - m2;
        }
        syear = m1;
        arrayLDAY[1] = m2 - 337;

        m1 = 0;

        while(true) {
            m1++;
            if (td <= arrayLDAY[m1-1]) {
                break;
            }
            td = td - arrayLDAY[m1-1];
        }
        smonth = m1;
        sday = td;
        int y = syear - 1;
        td = (int)(y*365) + (int)(y/4) - (int)(y/100) + (int)(y/400);

        if (syear % 400 == 0 || syear % 100 != 0 && syear % 4 == 0) {
            leap = 1;
        } else {
            leap = 0;
        }

        if (leap == 1) {
            arrayLDAY[1] = 29;
        } else {
            arrayLDAY[1] = 28;
        }
        for (int i=0; i <= smonth-2; i++) {
            td = td + arrayLDAY[i];
        }
        td = td + sday;
        int w = td % 7;

        String sweek = arrayWEEK[w];
        gf_lun2sol = 1;

        return String.valueOf(syear+String.format("%02d", smonth)+String.format("%02d", sday));
    }

    public static String sol2lun(String yyyymmdd) {
        int gf_sol2lun = 0;
        int gf_yun = 0;

        int getYEAR = Integer.parseInt(yyyymmdd.substring(0,4));
        int getMONTH = Integer.parseInt(yyyymmdd.substring(4,6));
        int getDAY = Integer.parseInt(yyyymmdd.substring(6,8));

        String arrayYUKSTR="甲-乙-丙-丁-戊-己-庚-辛-壬-癸";
        String[] arrayYUK = arrayYUKSTR.split("-");

        String arrayGAPSTR="子-丑-寅-卯-辰-巳-午-未-申-酉-戌-亥";
        String[] arrayGAP = arrayGAPSTR.split("-");

        String arrayDDISTR="쥐-소-범-토끼-용-뱀-말-양-원숭이-닭-개-돼지";
        String[] arrayDDI = arrayDDISTR.split("-");

        String arrayWEEKSTR="일-월-화-수-목-금-토";
        String[] arrayWEEK = arrayWEEKSTR.split("-");


        Long[] dt = new Long[LunarInfo.length];
        for (int i = 0; i < LunarInfo.length; i++) {
            dt[i] = Long.parseLong(LunarInfo[i]);
        }
        for (int i=0; i <= 168; i++) {
            dt[i] = Long.valueOf(0);
            for (int j=0;j < 12;j++) {
                switch (Integer.parseInt(LunarInfo[i].substring(j,j+1))) {
                    case 1:
                        dt[i] += 29;
                        break;
                    case 3:
                        dt[i] += 29;
                        break;
                    case 2:
                        dt[i] += 30;
                        break;
                    case 4:
                        dt[i] += 30;
                        break;
                }
            }

            switch (Integer.parseInt(LunarInfo[i].substring(12,13))) {
                case 0:
                    break;
                case 1:
                    dt[i] += 29;
                    break;
                case 3:
                    dt[i] += 29;
                    break;
                case 2:
                    dt[i] += 30;
                    break;
                case 4:
                    dt[i] += 30;
                    break;
            }
        }

        int td1 = 1880 * 365 + (int)(1880/4) - (int)(1880/100) + (int)(1880/400) + 30;
        int k11 = getYEAR - 1;

        int td2 = k11 * 365 + (int)(k11/4) - (int)(k11/100) + (int)(k11/400);

        if (getYEAR % 400 == 0 || getYEAR % 100 != 0 && getYEAR % 4 == 0) {
            arrayLDAY[1] = 29;
        } else {
            arrayLDAY[1] = 28;
        }

        if (getMONTH > 13) {
            gf_sol2lun = 0;
        }

        if (getDAY > arrayLDAY[getMONTH-1]) {
            gf_sol2lun = 0;
        }

        for (int i=0;i <= getMONTH-2;i++) {
            td2 += arrayLDAY[i];
        }

        td2 += getDAY;
        int td = td2 - td1 + 1;
        Long td0 = dt[0];

        int jcount = 0;
        int ryear = 0;
        int m1 = 0;
        int m2 = 0;
        int i = 0;
        for (i=0; i <= 168; i++) {
            if (td <= td0) {
                break;
            }
            td0 += dt[i + 1];
        }
        ryear = i + 1881;
        td0 -= dt[i];
        td -= td0;

        if (Integer.parseInt(LunarInfo[i].substring(12,13)) == 0) {
            jcount = 11;
        } else {
            jcount = 12;
        }

        for (int j=0;j <= jcount;j++) { // 달수 check, 윤달 > 2 (by harcoon)
            if (Integer.parseInt(LunarInfo[i].substring(j,j+1)) <= 2) {
                m2++;
                m1 = Integer.parseInt(LunarInfo[i].substring(j,j+1)) + 28;
                gf_yun = 0;
            } else {
                m1 = Integer.parseInt(LunarInfo[i].substring(j,j+1)) + 26;
                gf_yun = 1;
            }
            if (td <= m1) {
                break;
            }
            td = td - m1;
        }

        int k1=(ryear+6) % 10;
        String syuk = arrayYUK[k1];
        int k2=(ryear+8) % 12;
        String sgap = arrayGAP[k2];
        String sddi = arrayDDI[k2];

        gf_sol2lun = 1;

        return String.valueOf(ryear+String.format("%02d", m2)+String.format("%02d", td)+"|"+syuk+sgap+"년|"+sddi+"띠");
    }

}


샘플 코드

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.tistory.link2me.util.LunarCalendar;
import java.util.Calendar;

public class CalendarActivity extends AppCompatActivity {

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

        System.out.println("이순신 음력 => 양력 : "+ Lunar2Solar.lun2sol("19961112"));
        System.out.println("홍길동 양력 => 음력 : "+ Lunar2Solar.sol2lun("20010125"));
    }

}
 


Logcat

I/System.out: 이순신 음력 => 양력 : 19961222
I/System.out: 홍길동 양력 => 음력 : 2001|01|02|신사년|뱀띠

이 음력/양력 변환 코드가 자바 버전으로 돌아다니는 것은 없는거 같다.

PHP 코드를 자바로 변환한 것이지만 나름 유용하게 사용할 수 있을 것으로 본다.


이 글이 도움 되었다면 XX 클릭 해주세요. 좋은 자료 올리는데 큰 힘이 됩니다.

728x90
블로그 이미지

Link2Me

,