728x90

안녕하세요.

제 블로그에 오신 분들, 행복소통 청원 지지 좀 부탁드리겠습니다.


청원하기 URL

https://www.seongnam.go.kr/cheongwon/cheongwonView.do?menuIdx=1001464&idx=1129


성남 용인지역의 오리·동천역 SRT 역사 추가 설치 사업의 타당성 조사 진행 요청


휴대폰 청원 지지 방법 안내













'알면 좋은 정보' 카테고리의 다른 글

재미로 보는 관상  (0) 2018.07.12
청춘  (0) 2015.11.11
혈액형과 성격구분  (0) 2013.06.30
블로그 이미지

Link2Me

,
728x90

y = f(x) 형태의 함수로 구성된 프로그래밍 기법

Java 8에서 함수형 프로그래밍 방식인 람다식을 지원한다.

객체지향 프로그래밍보다는 함수형 프로그래밍에 가깝다고 할 수 있다.

 

람다식이란? 함수(메서드)를 간단한 ‘식(Expression)’으로 표현하는 방법

 

- 함수는 클래스에 독립적, 메소드는 클래스에 종속적
- 메서드의 이름과 반환타입을 제거하고 ‘->’를 블록{} 앞에 추가한다.
- 반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능(끝에‘;’안 붙임)
- 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능)
- 매개변수가 하나인 경우, 괄호() 생략가능(타입이 없을 때만)
- 블록 안의 문장이 하나뿐 일 때, 괄호{}생략가능(끝에 ‘;’ 안 붙임)
- 단, 하나뿐인 문장이 return문이면 괄호{} 생략불가

 

 

람다식 장점
1. 코드를 간결하게 만들 수 있다.
2. 코드가 간결하고 식에 개발자의 의도가 명확히 드러나므로 가독성이 향상된다.
3. 함수를 만드는 과정없이 한번에 처리할 수 있기에 코딩하는 시간이 줄어든다.
4. 병렬프로그래밍이 용이하다.

람다식 단점
1. 람다를 사용하면서 만드는 익명함수는 재사용이 불가능하다.
2. 디버깅이 다소 까다롭다.
3. 람다를 남발하면 코드가 지저분해질 수 있다.
4. 재귀로 만들경우에는 다소 부적합한면이 있다.

 

 
@FunctionalInterface
interface MyFunction {
    // 함수형 인터페이스 : 단 하나의 추상 메소드만 선언된 인터페이스
    int max(int a, int b);  // public abstract 생략
}
 
public class LambdaEX1 {
    public static void main(String[] args) {
        // 람다식(익명 객체)을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다.
        MyFunction f = (a, b) -> a > b ? a : b;
        int value = f.max(3,5);
        System.out.println(value);
    }
}
 

 

@FunctionalInterface
interface MyFunctionalInterface{
    // 함수형 인터페이스 : 단 하나의 추상 메소드만 선언된 인터페이스
    void method();
}
 
public class LambdaEX2 {
    public static void main(String[] args) {
        MyFunctionalInterface fi1 = new MyFunctionalInterface() {
            @Override
            public void method() {
                String str = "직접 익명 객체 구현함";
                System.out.println(str);
            }
        };
        fi1.method();
 
        MyFunctionalInterface fi2 = () -> {
            String str = "람다식 익명 객체 구현함";
            System.out.println(str);
        };
        fi2.method();
    }
}
 

 

 

 

 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
 
@FunctionalInterface
interface MyFunctionIF {
    // 함수형 인터페이스 : 단 하나의 추상 메소드만 선언된 인터페이스
    int max(int a, int b);  // public abstract 생략
}
 
public class LambdaEX1 {
    public static void main(String[] args) {
        // 람다식(익명 객체)을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다.
        MyFunctionIF f = (a, b) -> a > b ? a : b;
        int value = f.max(3,5);
        System.out.println(value);
 
        int result = Integer.parseInt("123");
        System.out.println(result);
 
        System.out.println("===================================");
        ArrayList<Integer> list = new ArrayList<>();
        for(int i=0;i < 20;i++)
            list.add(i);
 
        System.out.println(list);
 
        // list의 모든 요소를 출력
        list.forEach(i->System.out.print(i + ", "));
        System.out.println();
 
        // list에서 2 또는 3의 배수를 제거한다.
        list.removeIf(x -> x%2==0 || x%3==0);
        System.out.println(list);
 
        list.replaceAll(i->i*3); // list의 각 요소에 3을 곱한다.
        System.out.println(list);
 
        System.out.println("===================================");
        Map<StringString> map = new HashMap<>();
        map.put("1""1");
        map.put("2""2");
        map.put("3""3");
        map.put("4""4");
 
        // map의 모든 요소를 {k,v}의 형식으로 출력한다.
        map.forEach((k,v)-> System.out.print("{"+k+","+v+"},"));
        System.out.println();
    }
}
 

 

메소드 참조

메소드 레퍼런스(Method Reference)는 Lambda 표현식을 더 간단하게 표현하는 방법이다.

메소드 레퍼런스는 ClassName::MethodName 형식으로 입력한다. 
메소드를 호출하는 것이지만 괄호()는 써주지 않고 생략한다.

import java.util.Arrays;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.IntBinaryOperator;
 
class MathBox {
    // 클래스::메소드 --> 정적(static) 메소드 참조
    public static int staticPlus(int x, int y){
        return x+y;
    }
 
    // 참조변수::메소드 --> 인스턴스 메소드 참조
    public int sum(int x, int y){
        return x+y;
    }
 
    // 정수 빼기
    public int sub(int x, int y){
        return x-y;
    }
 
    // 정수 나누기
    public int division(int x, int y){
        return x / y;
    }
 
    // 실수 나누기
    public Float float_div(Float x, Float y){
        return x / y;
    }
 
}
 
public class Method_References {
    public static void main(String[] args) {
        List<String> strList = Arrays.asList("안녕""하세요");
        strList.stream().forEach(System.out::println);
 
        // static 메소드 참조
        IntBinaryOperator operator1;
        // 메소드 참조해서 매개변수 리턴타입 알아내 람다식에서 불필요한 매개변수 제거 목적
        operator1 = MathBox::staticPlus; // 메소드 참조
        System.out.println(operator1.applyAsInt(53));
 
        operator1 = (x, y) -> MathBox.staticPlus(x, y);
        System.out.println(operator1.applyAsInt(35));
 
        // instance 메소드 참조
        MathBox calculator = new MathBox();
        IntBinaryOperator operator2 = calculator::sum; // 메소드 참조
        System.out.println("더하기 : " + operator2.applyAsInt(1132));
 
        operator1 = (x, y) -> calculator.sum(x, y);
        System.out.println(operator1.applyAsInt(1132));
 
        operator2 = calculator::sub; // 메소드 참조
        System.out.println("빼기 : " + operator2.applyAsInt(1130));
 
        operator2 = calculator::division; // 메소드 참조
        System.out.println("나누기 : " + operator2.applyAsInt(154));
 
        BinaryOperator<Integer> operator3 = (n1, n2) -> n1 + n2; // 타입 추론
        Integer sum = operator3.apply(10100);
        System.out.println(sum);
 
        BinaryOperator<Float> operator4 = calculator::float_div;
        System.out.println("나누기 : " + operator4.apply(15.0f, 4.0f));
 
        BinaryOperator<Float> operator5 = (n1, n2) -> n1 / n2; // 타입 추론
        Float div = operator5.apply(15.0f, 4.0f);
        System.out.println(div);
    }
}
 

 

 

자바에서 제공하는 함수형 인터페이스를 이용하면 인터페이스를 만들지 않고 간단하게 코드 구현 가능하다.

import java.util.function.Consumer;
 
interface Printable {
    void doSomething(String text);
}
 
public class MethodReferencesEX2 {
    public static class Printer {
        static void printSomething(String text) {
            System.out.println(text);
        }
    }
 
    public static void main(String[] args) {
        Printable printable = text -> Printer.printSomething(text);
        printable.doSomething("do something");
 
        Printable printable2 = Printer::printSomething;
        printable2.doSomething("do something");
 
        // Consumer<클래스>는 자바에서 제공하는 함수형 인터페이스
        // String 1개를 인자로 받아 void를 리턴하는 메소드를 갖고 있는 인터페이스
        Consumer<String> consumer = Printer::printSomething;
        consumer.accept("do something"); // Printable 인터페이스를 만들 필요가 없다.
    }
}
 

 

블로그 이미지

Link2Me

,
728x90

Retrofit2 라이브러리 Java 버전 코딩을 Kotlin 으로 변환해서 이용하는 법을 적어둔다.

Android Studio 에서 Java 파일 Kotlin 파일로 변환하는 기능을 이용하여 변환하고 나서 에러가 나는 부분을 수정하면 대부분 해결이 된다.

static 은 object 파일로 변환을 해버리는데, 여기서는 companion object 를 사용해서 수동 변환을 했다.


public class RetrofitAPI {
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        retrofit = new Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build();

        return retrofit;
    }

    private static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }
}

class RetrofitAPI {

    companion object{
        private var retrofit: Retrofit? = null

        fun getClient(): Retrofit? {
            retrofit = Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build()
            return retrofit
        }

        private fun createOkHttpClient(): OkHttpClient? {
            // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
            val builder = OkHttpClient.Builder()
            val interceptor =
                HttpLoggingInterceptor()
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
            builder.addInterceptor(interceptor)
            return builder.build()
        }
    }

}


public interface RemoteService {// 서버에 호출할 메소드를 선언하는 인터페이스
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(RetrofitUrl.URL_LOGIN)
    Call<LoginResult> Login(
            @Field("userid") String userId,
            @Field("userpw") String userPw,
            @Field("token") String token,
            @Field("keyword") String keyword,
            @Field("mfoneNO") String mfoneNO,
            @Field("uuid") String uuid
    );
}

interface RemoteService { // 서버에 호출할 메소드를 선언하는 인터페이스
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(RetrofitUrl.URL_LOGIN)
    fun Login(
        @Field("userid") userId: String,
        @Field("userpw") userPw: String,
        @Field("token") token: String,
        @Field("keyword") keyword: String,
        @Field("mfoneNO") mfoneNO: String,
        @Field("uuid") uuid: String
    ): Call<LoginResult>
}



로그인 예제 변환

void AutoLoginProgress() { // 자동 로그인 체크 검사
    //Log.e("AutoLogin", "Auto Login Stage.");
    pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
    userID = pref.getString("userid", "");
    userPW = pref.getString("userpw", "");
    String keyword = Value.encrypt(Value.URLkey());
    String mfoneNO = Value.encrypt(getPhoneNumber());
    uID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);

    if (userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()) {
        mloginService = RetrofitAPI.getClient().create(RemoteService.class);
        mloginService.Login(Value.encrypt(userID), Value.encrypt(userPW),"", keyword, mfoneNO, Value.encrypt(uID))
                .enqueue(new Callback<LoginResult>() {
                    @Override
                    public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
                        // 네트워크 통신 성공
                        LoginResult result = response.body();
                        if(result.getResult().contains("success")){

                            Log.e("Intor UserName",result.getUserinfo().getUsername());

                            Intent intent = new Intent(Intro.this, MapViewActivity.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
                            startActivity(intent);
                            finish();

                        } else {
                            if(result.getResult().contains("로그인 에러")){
                                startActivity(new Intent(Intro.this, LoginActivity.class));
                                finish();
                            } else {
                                Utils.showAlert(Intro.this,result.getResult(),result.getMessage());
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call<LoginResult> call, Throwable t) {

                    }
                });

    } else {
        startActivity(new Intent(getApplication(), LoginActivity.class));
        finish();
    }

}


fun AutoLoginProgress() { // 자동 로그인 체크 검사
     //Log.e("AutoLogin", "Auto Login Stage.");
     pref = getSharedPreferences("pref", Activity.MODE_PRIVATE)
     userID = pref?.getString("userid", "")
     userPW = pref?.getString("userpw", "")
     val keyword = Value.encrypt(Value.URLkey())
     val mfoneNO = Value.encrypt(phoneNumber)
     uID = Settings.Secure.getString(applicationContext.contentResolver,Settings.Secure.ANDROID_ID)
     if (userID != null && !userID!!.isEmpty() && userPW != null && !userPW!!.isEmpty()) {
         mloginService = RetrofitAPI.getClient()!!.create(RemoteService::class.java)
         mloginService!!.Login(Value.encrypt(userID), Value.encrypt(userPW),"", keyword, mfoneNO,Value.encrypt(uID))
             ?.enqueue(object : Callback<LoginResult> {
                 override fun onResponse(
                     call: Call<LoginResult>,
                     response: Response<LoginResult>
                 ) {
                     // 네트워크 통신 성공
                     val result = response.body()
                     if (result!!.result.contains("success")) {
                         Log.e("Intor UserName", result.userinfo!!.username)
                         val intent = Intent(this@Intro, MapViewActivity::class.java)
                         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NO_HISTORY)
                         startActivity(intent)
                         finish()
                     } else {
                         if (result.result.contains("로그인 에러")) {
                             startActivity(Intent(this@Intro,LoginActivity::class.java))
                             finish()
                         } else {
                             Utils.showAlert(this@Intro,result.result,result.message)
                         }
                     }
                 }

                 override fun onFailure(
                     call: Call<LoginResult?>,
                     t: Throwable
                 ) {
                 }
             })
     } else {
         startActivity(Intent(application,LoginActivity::class.java))
         finish()
     }
 }


블로그 이미지

Link2Me

,
728x90

앱을 만들다보면 앱을 실행하면서 현재 위치 좌표값만 획득하고 싶을 경우가 있다.


AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Android 10 이상 백그라운드 위치 액세스 요청 --> 


앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation 'com.naver.maps:map-sdk:3.7.1' // 네이버 지도 SDK

}


SplashActivity.java

public class SplashActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;

    private final int GPS_ENABLE_REQUEST_CODE = 2001;
    private final long UPDATE_INTERVAL_MS = 1000 * 60 * 15;
    private final long FASTEST_UPDATE_INTERVAL_MS = 1000 * 30 ; // 30초 단위로 화면 갱신

    private FusedLocationProviderClient mFusedLocationProviderClient;
    private LocationRequest locationRequest;
    private double longitude, latitude;

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

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        mContext = SplashActivity.this;
        PrefsHelper.init(getApplicationContext()); // 한번만 실행하면 된다.

        checkPermissions();
    }

    private void checkPermissions() {

        if(Build.VERSION.SDK_INT >= 26){ // 출처를 알 수 없는 앱 설정 화면 띄우기
            PackageManager pm = mContext.getPackageManager();
            if (!pm.canRequestPackageInstalls()){
                startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
                        Uri.parse("package:" + getPackageName())));
            }
        }

        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(mContext)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.ACCESS_FINE_LOCATION,
                            android.Manifest.permission.ACCESS_COARSE_LOCATION
                            //android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
                            //앱이 Android 10 이상 앱이 백그라운드에 있는 동안 정기 위치 업데이트를 받을 시
                    })
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        checkLocationSetting();
    }

    @SuppressLint("MissingPermission")
    private void checkLocationSetting() {
        // https://developer.android.com/training/location/change-location-settings 읽어라.
        // 위치 요청 설정
        locationRequest = LocationRequest.create();
        locationRequest.setInterval(UPDATE_INTERVAL_MS); // 위치가 Update 되는 주기
        locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_MS); // 위치 획득후 업데이트되는 주기
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        // 현재 위치 설정 받기
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest);
        SettingsClient settingsClient = LocationServices.getSettingsClient(this);
        settingsClient.checkLocationSettings(builder.build())
                .addOnSuccessListener(this, locationSettingsResponse -> {
                    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(SplashActivity.this);
                    mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, null);
                })
                .addOnFailureListener(SplashActivity.this, e -> {
                    int statusCode = ((ApiException) e).getStatusCode();
                    switch (statusCode) {
                        case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                            try {
                                ResolvableApiException rae = (ResolvableApiException) e;
                                rae.startResolutionForResult(SplashActivity.this, GPS_ENABLE_REQUEST_CODE);
                            } catch (IntentSender.SendIntentException sie) {
                                Log.e(TAG, "unable to start resolution for result due to " + sie.getLocalizedMessage());
                            }
                            break;
                        case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                            Log.e(TAG, "Fix in Settings.");
                    }
                });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == GPS_ENABLE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                checkLocationSetting();
            } else {
                finish();
            }
        }
    }

    private LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            longitude = locationResult.getLastLocation().getLongitude();
            latitude = locationResult.getLastLocation().getLatitude();
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);

            Log.e(TAG, "latitude:"+latitude+" , longitude:"+longitude);
            PrefsHelper.write("latitude", String.valueOf(latitude));
            PrefsHelper.write("longitude", String.valueOf(longitude));

            Intent intent = new Intent(SplashActivity.this, MainActivity.class);
            startActivity(intent);
            finish();
        }

        @Override
        public void onLocationAvailability(LocationAvailability locationAvailability) {
            super.onLocationAvailability(locationAvailability);
            Log.i(TAG, "onLocationAvailability - " + locationAvailability);
        }
    };

}
 


PrefsHelper.java

public class PrefsHelper {
    public static final String PREFERENCE_NAME="pref";
    private Context mContext;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor prefsEditor;
    private static PrefsHelper instance;

    public static synchronized PrefsHelper init(Context context){
        if(instance == null)
            instance = new PrefsHelper(context);
        return instance;
    }

    private PrefsHelper(Context context) {
        mContext = context;
        prefs = mContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE );
        prefsEditor = prefs.edit();
    }

    public static String read(String key, String defValue) {
        return prefs.getString(key, defValue);
    }

    public static void write(String key, String value) {
        prefsEditor.putString(key, value);
        prefsEditor.commit();
        // double 변수 값은 string으로 감싸서 데이터를 넘기면 된다.
    }

    public static Integer read(String key, int defValue) {
        return prefs.getInt(key, defValue); // 32비트
    }

    public static void write(String key, Integer value) {
        prefsEditor.putInt(key, value).commit();
    }

    public static long read(String key, Long defValue) {
        return prefs.getLong(key, defValue); // 64비트
    }

    public static void write(String key, Long value) {
        prefsEditor.putLong(key, value).commit();
    }

    public static boolean read(String key, boolean defValue) {
        return prefs.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }
}


지도를 띄우지 않은 상태에서 현재 위치 좌표만 얻어지는 걸 확인할 수 있다.


참고하면 좋은 게시글

https://medium.com/@droidbyme/android-turn-on-gps-programmatically-d585cf29c1ef



블로그 이미지

Link2Me

,
728x90

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

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


MainActivity.java

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

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

    RemoteService remoteService;

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

    private BackPressHandler backPressHandler;

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

        initView();
    }

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

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

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

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

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

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

        send.setOnClickListener(v ->

                items_builder.show());

    }

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

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

        mAdapter.setOnItemSelectClickListener(this);
    }

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

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

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


RecyclerViewAdapter.java

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    }

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

}


Address_Item.kt

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

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


address_item.xml

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

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

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

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

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

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

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

        </LinearLayout>

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

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

        </LinearLayout>

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

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



블로그 이미지

Link2Me

,
728x90

메인 화면 구성을 위해서 구글에서 Github 검색으로 https://github.com/Whhoesj/sliding-content-drawer 를 하나 찾아서 테스트하고 간단하게 적어둔다.

 


화면 구성은 위와 같다.

https://link2me.tistory.com/1810 에 Navigation Drawer 구성에 대한 개념이 설명되어 있는데, SlidingContentDrawer 는 아래 Java 코드만 살펴보면 이해될 것이다.


Project build.gradle

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://navercorp.bintray.com/maps' }
        maven { url "https://jitpack.io" }
    }
}


앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'com.wouterhabets:slidingcontentdrawer:1.0'
    implementation 'com.naver.maps:map-sdk:3.7.1' // 네이버 지도 SDK
    implementation 'com.google.android.gms:play-services-location:17.0.0'
}


AndroidManifest.xml

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="false"
        android:theme="@style/AppTheme.NoActionBar">
        <activity android:name=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <meta-data
            android:name="com.naver.maps.map.CLIENT_ID"
            android:value="@string/naver_app_key" />
    </application>

</manifest>


strings.xml

<resources>
    <string name="app_name">SlidingContentDrawer</string>
    <string name="navigation_drawer_open">Open navigation drawer</string>
    <string name="navigation_drawer_close">Close navigation drawer</string>
    <string name="naver_app_key">여기에 키를 적어주세요</string>
</resources>


Drawer Layout 구성

- Layout 구성을 버전에 맞춰 변경 수정했다.

- 파일을 3개의 파일로 분리 구성했다.

<?xml version="1.0" encoding="utf-8"?>
<com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerLayout
    android:id="@+id/drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/colorPrimaryDark"
    android:fitsSystemWindows="true">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/background"/>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:tag="menu">

        <include layout="@layout/drawer_menu"/>

    </FrameLayout>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:colorBackground"
        android:elevation="6dp"
        android:fitsSystemWindows="true"
        android:tag="content">

        <View
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:background="@android:color/holo_green_light"/>

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </com.google.android.material.appbar.AppBarLayout>

        <include
            layout="@layout/drawer_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            android:src="@android:drawable/ic_dialog_email"/>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerLayout>

<?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="wrap_content"
    android:layout_gravity="start|center_vertical"
    android:orientation="vertical"
    android:paddingLeft="16dp">

    <TextView
        android:id="@+id/navermap_to"
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="네이버 지도"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 2"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 3"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 4"
        android:layout_marginBottom="30dp"
        android:textColor="@android:color/white"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:layout_gravity="start|center_vertical"
        android:text="Menu item 5"
        android:textColor="@android:color/white"/>
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@string/app_name"/>
    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>



MainActivity.java 구현

package com.link2me.android.slidingcontentdrawer;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.link2me.android.common.BackPressHandler;
import com.link2me.android.common.PrefsHelper;
import com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerLayout;
import com.wouterhabets.slidingcontentdrawer.widget.SlidingDrawerToggle;

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        initView();
    }

    private void initView() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        SlidingDrawerLayout drawer = (SlidingDrawerLayout) findViewById(R.id.drawer_layout);
        SlidingDrawerToggle toggle = new SlidingDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        // 메뉴 버튼 클릭 리스너
        findViewById(R.id.navermap_to).setOnClickListener(this);
    }

    @Override
    public void onBackPressed() {
        SlidingDrawerLayout drawer = (SlidingDrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen()) {
            drawer.closeDrawer();
        } else {
            backPressHandler.onBackPressed();
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.navermap_to:
                Intent intent = new Intent(mContext,MapActivity.class);
                startActivity(intent);
                break;
        }
    }
}


MapActivity.java 구현

package com.link2me.android.slidingcontentdrawer;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.drawerlayout.widget.DrawerLayout;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.link2me.android.common.PrefsHelper;
import com.naver.maps.geometry.LatLng;
import com.naver.maps.map.CameraAnimation;
import com.naver.maps.map.CameraUpdate;
import com.naver.maps.map.LocationTrackingMode;
import com.naver.maps.map.MapView;
import com.naver.maps.map.NaverMap;
import com.naver.maps.map.OnMapReadyCallback;
import com.naver.maps.map.UiSettings;
import com.naver.maps.map.overlay.InfoWindow;
import com.naver.maps.map.util.FusedLocationSource;
import com.naver.maps.map.widget.LocationButtonView;

public class MapActivity extends AppCompatActivity implements OnMapReadyCallback {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;

    // NaverMap API 3.0
    private MapView mMapView;
    private LocationButtonView locBtnView;
    DrawerLayout drawer_layout;
    ImageView btn_drawer;
    CardView cardView;

    // FusedLocationSource (Google)
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
    private FusedLocationSource locationSource;

    private boolean  isFirstLocation = true;
    private NaverMap mNaverMap;
    private InfoWindow mInfoWindow; // 지도 위에 올리는 정보 창을 정의
    private boolean showMarket = false;

    String latitude;
    String longitude;

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

        // https://console.ncloud.com/mc/solution/naverService/application 에서 패키지명 등록 확인부터하라.
        mMapView = findViewById(R.id.map_view);
        mMapView.onCreate(savedInstanceState);
        mMapView.getMapAsync(this);

        // 내 현재 위치 버튼
        locBtnView = findViewById(R.id.btn_curr_location);
        // implementation "com.google.android.gms:play-services-location:17.0.0" 안해주면 에러 발생
        // 내 위치 찾기 위한 소스
        locationSource = new FusedLocationSource(this,LOCATION_PERMISSION_REQUEST_CODE);

        FloatingActionButton fab_message = findViewById(R.id.fab_message);
        fab_message.setOnClickListener(view -> {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        });
    }

    @Override
    public void onMapReady(@NonNull NaverMap naverMap) {
        UiSettings uiSettings = naverMap.getUiSettings();
        uiSettings.setLocationButtonEnabled(false);
        uiSettings.setCompassEnabled(true);

        // 카메라 초기 위치 설정
        LatLng initialPosition = new LatLng(37.506855, 127.066242);

        mNaverMap = naverMap;

        locBtnView.setMap(naverMap);

        // Location Change Listener를 사용하기 위한 FusedLocationSource 설정
        naverMap.setLocationSource(locationSource);
        naverMap.setLocationTrackingMode(LocationTrackingMode.NoFollow); // LocationTrackingMode.Face

        naverMap.addOnLocationChangeListener(location -> {
            if (isFirstLocation) { // 현재 위치로 초기 화면 이동
                naverMap.setLocationTrackingMode(LocationTrackingMode.None);
                LatLng initialPosition1 = new LatLng(location.getLatitude(),location.getLongitude()); // 현재 위치
                latitude = String.valueOf(location.getLatitude());
                longitude = String.valueOf(location.getLongitude());
                PrefsHelper.write("latitude",latitude);
                PrefsHelper.write("longitude",longitude);
                //Log.e(TAG,"latitude:"+latitude+" , longitude:"+longitude);
                CameraUpdate cameraUpdate = CameraUpdate.scrollTo(initialPosition1).animate(CameraAnimation.Easing);
                naverMap.moveCamera(cameraUpdate);
            }
            isFirstLocation = false;
        });

    }
}


지도 처리를 위해서는 Naver Cloud 에 앱패키지명을 등록해야 한다.


테스트에 사용한 파일을 첨부한다.

slidingcontentdrawer.zip


블로그 이미지

Link2Me

,
728x90

작성일자 : 2020.7.14


Retrofit2 라이브러리를 이용하여 데이터 처리하는 걸 테스트하다가 완전 삽질을 한참 했다.

Array로 전체 데이터를 받아서 처리해야 하는데 그렇게 처리하지 않아서 헤메고 있었다.


Retrofit2 기본 개념은 https://link2me.tistory.com/1806 잘 정리되어 있다.


public interface DataCommAPI {
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(Value.AddressData)
    Call<List<Address_Item>> GetAddrData(
            @Field("keyword") String keyword,
            @Field("search") String search
    );
}

@Parcelize
data class Address_Item (
    // 결과를 받을 모델
    var uid: String="",
    var userNM: String="",
    var mobileNO: String?=null,
    var telNO: String?=null,
    var photo: String?=null,
    var checkBoxState: Boolean
): Parcelable


MainActivity.java

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

    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버에서 가져온 원본 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;
    private SearchView editsearch;

    DataCommAPI dataCommAPI;

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

    RelativeLayout relative2;
    private BackPressHandler backPressHandler;

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

        initView();
    }

    private void initView() {
        isMSG = false;
        isAll = false;
        // Relative layout 정의
        relative2 = (RelativeLayout) findViewById(R.id.list_view_relative2);

        mRecyclerView = findViewById(R.id.address_listview);
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(context);
        DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);

        mAdapter = new RecyclerViewAdapter(context,addressItemList);
        mRecyclerView.setAdapter(mAdapter);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        getServerData(); // 서버 데이터 가져오기
    }

    private void getServerData() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build();

        dataCommAPI = retrofit.create(DataCommAPI.class);

        String keyword = Value.encrypt(Value.URLkey());
        dataCommAPI.GetAddrData(keyword,"").enqueue(new Callback<List<Address_Item>>() {
            @Override
            public void onResponse(Call<List<Address_Item>> call, Response<List<Address_Item>> response) {
                List<Address_Item> result = response.body(); // 전체 데이터

                addressItemList.clear(); // 서버에서 가져온 데이터 초기화
                searchItemList.clear();
               
                for(Address_Item item : result){
                    addressItemList.add(item);
                    searchItemList.add(item);
                }
                runOnUiThread(new Runnable() { // runOnUiThread()를 호출하여 실시간 갱신한다.
                    @Override
                    public void run() {
                        // 갱신된 데이터 내역을 어댑터에 알려줌
                        mAdapter.notifyDataSetChanged();
                    }
                });
            }

            @Override
            public void onFailure(Call<List<Address_Item>> call, Throwable t) {

            }
        });

    }

    private OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

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



통신상태가 성공, 실패 여부를 먼저 확인한 다음에

ArrayList 데이터 전체를 출력하는 걸 처리하는 사항

@Parcelize
class AddressResult (
    val status: String,
    val message: String="",
    val addrinfo: List<Address_Item>? = null

): Parcelable
 

public interface RemoteService {
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(Value.AddressData)
    Call<AddressResult> GetAddrData(
            @Field("keyword") String keyword,
            @Field("search") String search
    );
}

private void getServerData() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Value.IPADDRESS)
            .addConverterFactory(GsonConverterFactory.create())
            .client(createOkHttpClient())
            .build();

    dataCommAPI = retrofit.create(RemoteService.class);

    String keyword = Value.encrypt(Value.URLkey());
    dataCommAPI.GetAddrData(keyword,"").enqueue(new Callback<AddressResult>() {
        @Override
        public void onResponse(Call<AddressResult> call, Response<AddressResult> response) {
            if(response.body().getStatus().contains("success")){
                addressItemList.clear(); // 서버에서 가져온 데이터 초기화
                searchItemList.clear();

                for(Address_Item item : response.body().getAddrinfo()){
                    addressItemList.add(item);
                    searchItemList.add(item);
                }
                runOnUiThread(new Runnable() { // runOnUiThread()를 호출하여 실시간 갱신한다.
                    @Override
                    public void run() {
                        // 갱신된 데이터 내역을 어댑터에 알려줌
                        mAdapter.notifyDataSetChanged();
                    }
                });
            } else {
                showAlert(response.body().getStatus(),response.body().getMessage());
            }
        }

        @Override
        public void onFailure(Call<AddressResult> call, Throwable t) {

        }
    });

}


추가 오픈일자 : 2020.9.15일

위 안드로이드 코드와 관련된 PHP 코드

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // POST 전송으로 전달받은 값 처리
    require_once 'config/config.php';
    require_once 'phpclass/dbconnect.php';
    require_once 'phpclass/loginClass.php';
    $c = new LoginClass;

    header("Cache-Control: no-cache, must-revalidate");
    header("Content-type: application/json; charset=UTF-8");

    $R = array(); // 결과 담을 변수 생성
    // 화면에 출력할 칼럼 발췌
    $sql = "select uid,userNM,mobileNO,telNO,photo from Person ";
    if(!empty($search)) {
        $sql .= "where userNM LIKE '%".$search."%' or mobileNO LIKE '%".$search."%'";
    }
    $result = mysqli_query($db,$sql);
    while($row = mysqli_fetch_array($result)) {
        $photo ="null";
        $photo_path = './photos/'.$row['photo'];
        if(file_exists($photo_path)) {
            $photo =$row['photo'];
        }
        array_push($R,  array("uid"=>$row['uid'],"userNM"=>$row['userNM'],"mobileNO"=>$row['mobileNO'],

                                        "telNO"=>$row['telNO'],"photo"=>$photo,"checkBoxState"=>false));
    }

    $status = "success";
    $message = "";
    $addrinfo = $R; // 전체 ArrayList 데이터

    $result = array(
        'status' => $status,
        'message' => $message,
        'addrinfo' => $addrinfo
    );
    echo json_encode($result);

}
?>


블로그 이미지

Link2Me

,
728x90

네이버 지오코더 API 를 이용하여 주소 → 위치좌표를 반환하는 PHP 코드와 네이버 지도상에 마커를 보여주는 Javascript 구현 코드 예제다.

IP주소 및 경로 등록이 되어있어야만 Naver Map 이 화면에 보여지며, Naver Cloud 신청에 대한 사항은 https://link2me.tistory.com/1832 게시글을 참조하면 된다.

 

주소 → 위치좌표 변환을 위해서는 Naver Cloud 에서 Geocorder API가 신청되어 있어야 한다.

 

Web 브라우저에 보이기 위해서는 Web Dynamic Map 이 신청되어 있어야 한다.

 

 

<?php
//error_reporting(0);
//error_reporting(E_ALL);
//ini_set("display_errors", 1);

function getNaverGeocode($addr) {
    $addr = urlencode($addr);
    $url = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode?query=".$addr;
    $headers = array();
    $headers[] ="X-NCP-APIGW-API-KEY-ID:xz06ez230a";
    $headers[] ="X-NCP-APIGW-API-KEY:e0A5km9e7Rh5S9QOkr1TRfa4NW8MaDblKAXcrQXL";

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}

$shop_name = "광제당한의원";
$addr = "경기도 안산시 단원구 선부광장1로 56";
$geo = getNaverGeocode($addr);
$data = json_decode($geo,1);

$map_y_point = $data['addresses'][0]['x']; // x 좌표값과 y좌표값이 바뀌어서 출력됨
$map_x_point = $data['addresses'][0]['y'];

$lat = $map_x_point;
$lng = $map_y_point;
?>
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="expires" content="0" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
<script src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=
xz06ez230a&submodules=geocoder"></script>

</head>
<body>
<div class="container-fluid text-center">
    <div class="row">
        <div class="col-md-12">
            <div class="content" id="content">
                <div id="map" style="width:100%;height:450px;"></div>
            </div>
        </div>
    </div>
</div>

<script>
var lat = "<?php echo $lat;?>";
var lng = "<?php echo $lng;?>";
var shop_name = "<?php echo $shop_name;?>";
var addr = "<?php echo $addr;?>";
var HOME_PATH = window.HOME_PATH || '.';

var map = new naver.maps.Map('map', {
   useStyleMap: true,
   center: new naver.maps.LatLng(lat, lng), //지도의 초기 중심 좌표
   zoom: 15, //지도의 초기 줌 레벨
   minZoom: 7, //지도의 최소 줌 레벨
   zoomControlOptions : { //줌 컨트롤의 옵션
       position : naver.maps.Position.TOP_RIGHT
   },
   mapTypeControl : true
});

// 마커 표시
var marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(lat, lng),
    map: map
});

var contentString = [
        '<div style="text-align:center;padding-left:15px;padding-right:15px;padding-top:5px;">',
        '   <h4>'+shop_name+'</h4>',
        '   <p>'+addr+'<br />',
        '   </p>',
        '</div>'
    ].join('');

var infowindow = new naver.maps.InfoWindow({
    content: contentString
});

naver.maps.Event.addListener(marker, "click", function(e) {
    if (infowindow.getMap()) {
        infowindow.close();
    } else {
        infowindow.open(map, marker);
    }
});

infowindow.open(map, marker);
</script>
</body>
</html>
 

 

참고사이트

https://github.com/navermaps/maps.js/tree/master/examples/map

 

https://navermaps.github.io/maps.js/docs/tutorial-3-geocoder-geocoding.example.html

 

https://navermaps.github.io/android-map-sdk/guide-ko/0.html

 

https://sir.kr/g5_tip/10880

 

 

블로그 이미지

Link2Me

,
728x90

이전 코드 구현에서 Gson 라이브러리를 사용하여 코드를 변경하는 부분이 핵심이다.


package com.link2me.android.recyclerviewjava.model

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

@Parcelize
data class Address_Item (
    // 결과를 받을 모델, 코틀린 혼용하다보니 코틀린의 문법 적용을 받게되어 null 처리를 조심해야 하더라.
    var uid: String="",
    var userNM: String="",
    var mobileNO: String?=null,
    var telNO: String?=null,
    var photo: String?=null,
    var checkBoxState: Boolean
): Parcelable


package com.link2me.android.recyclerviewjava;

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.RelativeLayout;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.link2me.android.recyclerviewjava.adapter.RecyclerViewAdapter;
import com.link2me.android.recyclerviewjava.model.Address_Item;
import com.link2me.android.recyclerviewjava.model.DataCommAPI;

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

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;

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

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

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

        initView();
    }

    private void initView() {
        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        getServerData(Value.IPADDRESS+"getJSONData.php"); // 서버 데이터 가져오기
    }

    private void getServerData(String SERVER_URL) {
        HashMap<String, String> params = new HashMap<>();
        params.put("keyword", Value.encrypt(Value.URLkey()));
        JSONObject jsonObject = new JSONObject(params);

        // 1. RequestQueue 생성 및 초기화
        RequestQueue requestQueue = Volley.newRequestQueue(context);

        // 2. Request Obejct인 StringRequest 생성
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, SERVER_URL,jsonObject,
                (Response.Listener<JSONObject>) response -> {
                    Log.e("result", "" + response);
                    try {
                        if(response.getString("status").equals("success")){
                            String jsondata = response.getString("addrinfo");
                            showJSONList(jsondata);
                        } else {
                            showAlert(response.getString("status"),response.getString("message"));
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                },
                error -> Log.d("error", "[" + error.getMessage() + "]")) {

        };

        // 3) 생성한 StringRequest를 RequestQueue에 추가
        requestQueue.add(request);
    }

    private void showJSONList(String JSONdata) {
        try {
            JSONArray peoples = new JSONArray(JSONdata); // [] 대괄호
            addressItemList.clear(); // 서버에서 가져온 데이터 초기화

            Gson gson = new Gson();
            Type listType = new TypeToken<ArrayList<Address_Item>>(){}.getType();
            addressItemList = gson.fromJson(peoples.toString(),listType);
            searchItemList = gson.fromJson(peoples.toString(),listType);

            mRecyclerView = findViewById(R.id.address_listview);
            mAdapter = new RecyclerViewAdapter(context,addressItemList);
            LinearLayoutManager manager = new LinearLayoutManager(context);
            DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());

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


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

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

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


블로그 이미지

Link2Me

,
728x90

오랫만에 서버에 있는 자료를 가져오는 걸 했는데 삽질을 한참이나 했다.

코틀린으로의 변환을 고려하여 앱 build.gradle은 아래와 같이 작성했다.


앱 build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"

    defaultConfig {
        applicationId "com.link2me.android.recyclerviewjava"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
    }

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

    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'  // 레이아웃으로 사용할 CardView
    implementation 'androidx.recyclerview:recyclerview:1.1.0'

    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.google.code.gson:gson:2.8.6'

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

}
 


모델 작성

- 코틀린으로 코드를 구현했다. Java 와 코틀린 혼용 코딩이 가능하다.

package com.link2me.android.recyclerviewjava.model

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

@Parcelize
data class Address_Item (
    // 결과를 받을 모델
    val uid: String="",
    val userNM: String="",
    val mobileNO: String="",
    val telNO: String="",
    val photo: String="",
    val checkBoxState: Boolean
): Parcelable


Gson 라이브러리를 사용했는데도 불구하고, 데이터가 반환되지 않는 원인을 찾고자 Retrofit2 라이브러리 대신에 Volley 라이브러리를 사용해서 테스트 했다. 두 라이브러리는 동시에 사용할 수가 없더라.

그래서 먼저 Gson 라이브러리 사용없이 for문으로 데이터를 저장하는 걸 시도했다.


package com.link2me.android.recyclerviewjava;

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.RelativeLayout;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.link2me.android.recyclerviewjava.adapter.RecyclerViewAdapter;
import com.link2me.android.recyclerviewjava.model.Address_Item;

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

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

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

    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 원본 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;

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

        initView();
    }

    private void initView() {
        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        getServerData(Value.IPADDRESS+"getJSONData.php"); // 서버 데이터 가져오기
    }

    private void getServerData(String SERVER_URL) {
        HashMap<String, String> params = new HashMap<>();
        params.put("keyword", Value.encrypt(Value.URLkey()));
        JSONObject jsonObject = new JSONObject(params);

        // 1. RequestQueue 생성 및 초기화
        RequestQueue requestQueue = Volley.newRequestQueue(context);

        // 2. Request Obejct인 StringRequest 생성
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, SERVER_URL,jsonObject,
                (Response.Listener<JSONObject>) response -> {
                    Log.e("result", "" + response);
                    try {
                        if(response.getString("status").equals("success")){
                            String jsondata = response.getString("addrinfo");
                            showJSONList(jsondata);
                        } else {
                            showAlert(response.getString("status"),response.getString("message"));
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                },
                error -> Log.d("error", "[" + error.getMessage() + "]")) {

        };

        // 3) 생성한 StringRequest를 RequestQueue에 추가
        requestQueue.add(request);
    }

    private void showJSONList(String JSONdata) {
        try {
            JSONArray peoples = new JSONArray(JSONdata); // [] 대괄호
            addressItemList.clear(); // 서버에서 가져온 데이터 초기화
            for(int i=0;i < peoples.length();i++){
                JSONObject jsonObj = peoples.getJSONObject(i);
                final String uid = jsonObj.getString("uid");
                final String name = jsonObj.getString("userNM");
                final String mobileNO = jsonObj.getString("mobileNO");
                final String telNO = jsonObj.getString("telNO");
                final String photo = jsonObj.getString("photo");

                getServerDataList(uid,name,mobileNO,telNO,photo,false);
                selectDataList(uid,name,mobileNO,telNO,photo,false);
            }

            mRecyclerView = findViewById(R.id.address_listview);
            mAdapter = new RecyclerViewAdapter(context,addressItemList);
            LinearLayoutManager manager = new LinearLayoutManager(context);
            DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());

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


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

    // 아이템 데이터 추가를 위한 메소드
    public void getServerDataList(String uid, String name, String mobileNO, String officeNO, String photo_image, boolean checkItem_flag) {
        Address_Item item = new Address_Item(uid,name,mobileNO,officeNO,photo_image,checkItem_flag);
        addressItemList.add(item);
    }

    public void selectDataList(String uid, String name, String mobileNO, String officeNO, String photo_image, boolean checkItem_flag) {
        Address_Item item = new Address_Item(uid,name,mobileNO,officeNO,photo_image,checkItem_flag);
        searchItemList.add(item);
    }

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

}


다른코드는 첨부파일을 참조하면 된다.

recyclerviewj_1.zip


블로그 이미지

Link2Me

,
728x90

Floating Action Button 은 화면에 떠있는 버튼으로 머터리얼 디자인에서 자주 사용하는 기본 위젯이다.

 

https://www.youtube.com/watch?v=cASXtRa6yDc 동영상을 보면 이해될 것이다.

 

 

앱 build.gradle 추가사항

dependencies {
    implementation 'com.google.android.material:material:1.1.0'
}

 

 

ConstraintLayout 의 모든 보기에는 가로 및 세로 제약조건을 각각 하나 이상 추가해야 한다.

 

 

아래 그림에서 ConstraintLayout 에서 버튼의 위치를 결정하는 것은 1번과 2번이다.

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

 

 

 

5번을 누르면 floating 버튼의 색상을 쉽게 변경할 수 있다.

6번은 floating 버튼 안의 이미지를 작성하는 것이다.

Vector Drawable을 사용하고 롤리팝 미만의 기기를 지원해야 할 때는 src 대신에 srcCompat 속성을 사용해야 한다.

 

 

 

 

검색으로 원하는 이미지를 찾아도 된다.

 

이름이 자동으로 추가된 것을 확인할 수 있다.

 

 

 

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fabMain"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:backgroundTint="#00BCD4"
        android:tooltipText="메시지 전송"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@drawable/ic_baseline_menu_24"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

public class MainActivity extends AppCompatActivity {

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

        FloatingActionButton fab = findViewById(R.id.fabMain);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view,"실제 클릭 이벤트를 대신 보여준다.",Snackbar.LENGTH_LONG)
                        .setAction("Action",null)
                        .show();
            }
        });
    }
}
 

 

테스트 파일 첨부 (Java 기반 코드인데 코틀린으로 변환 목적으로 앱 build.gradle이 코틀린 관련 추가되어 있음)

floatingbutton.zip
다운로드

 

코틀린으로 코드를 변환해보자.

 

자동변환 전후 Java코드와 kotlin 코드를 보자.

FloatingActionButton fab = findViewById(R.id.fabMain);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Snackbar.make(view,"실제 클릭 이벤트를 대신 보여준다.",Snackbar.LENGTH_LONG)
                .setAction("Action",null)
                .show();
    }
});

val fab = findViewById<FloatingActionButton>(R.id.fabMain)
fab.setOnClickListener { view ->
    Snackbar.make(view, "실제 클릭 이벤트를 대신 보여준다.", Snackbar.LENGTH_LONG)
        .setAction("Action", null)
        .show()
}

 

코틀린 코드를 좀 더 정리하면....

fabMain.setOnClickListener { view ->
    Snackbar.make(view, "실제 클릭 이벤트를 대신 보여준다.", Snackbar.LENGTH_LONG)
        .setAction("Action", null)
        .show()
}

 

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

SMS authentication with OTP Layout Example  (0) 2020.09.14
EditText DatePicker  (0) 2020.08.18
CardView Layout 예제  (0) 2020.04.12
Meterial Design 로그인 Layout 예제  (0) 2020.03.21
ScrollView  (0) 2019.12.20
블로그 이미지

Link2Me

,
728x90

다형성(Polymorphism)

- 같은 타입이지만 실행 결과가 다양한 객체 대입 가능한 성질

- 조상 타입의 참조변수로 자손 타입의 객체를 다룰 수 있는 것이 다형성

 

예제1)

public class Ex7_7 {
    public static void main(String[] args) {
        // 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것이 다형성
        // 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
        Car car = null; // 조상 타입의 참조변수
        FireEngine fe = new FireEngine(); // 실제 인스턴스가 무엇인지가 중요
       
        car = fe; // 자손 → 조상으로 형변환. 형변환 생략됨
        car.drive();
       
        FireEngine fe2 = null; // 자손 타입의 참조변수
        fe2 = (FireEngine) car; // 조상 → 자손으로 형변환. 형변환 생략불가.
        fe2.drive();
        fe2.water();
    }
}

class Car {
    String color;
    int door;
   
    void drive() {
        System.out.println("운전중!");
    }
   
    void stop() {
        System.out.println("정지.");
    }
}

class FireEngine extends Car {
    void water() {
        System.out.println("물 뿌리는 중!");
    }
}
 

 

예제1은 문제없이 잘 동작된다.

예제2는 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조할 수 없다는 사항에 위배된다.

 

예제2)

public class Ex7_7 {
    public static void main(String[] args) {
        // 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것이 다형성
        // 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
        Car car = new Car(); // 조상 인스턴스
        FireEngine fe = (FireEngine) car; // Car cannot be cast to FireEngine
        fe.drive();
    }
}

class Car {
    String color;
    int door;
   
    void drive() {
        System.out.println("운전중!");
    }
   
    void stop() {
        System.out.println("정지.");
    }
}

class FireEngine extends Car {
    void water() {
        System.out.println("물 뿌리는 중!");
    }
}
 

 

예제3)

public class Polymorphism {
    public static void main(String[] args) {
        // 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것이 다형성
        // 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
        Car car = null// 조상 타입의 참조변수
        FireEngine fe = new FireEngine(); // 실제 인스턴스가 무엇인지가 중요
 
        car = fe; // 자손 → 조상으로 형변환. 형변환 생략됨
        car.drive(); // 부모 타입에 선언된 변수와 메소드만 사용 가능
 
        FireEngine fe2 = null// 자손 타입의 참조변수
        if(car instanceof FireEngine){ // 먼저 자식 타입인지 확인 후 강제 타입 실행.
            System.out.println("===== 객체 타입 확인 =====");
            fe2 = (FireEngine) car; // 조상 → 자손으로 형변환. 형변환 생략불가
            fe2.drive();
            fe2.water(); // 자식 타입에 선언된 변수와 메소드를 다시 사용해야 하는 경우
        }
 
        Car car1 = new FireEngine(); // 부모 클래스 변수 = 자식 클래스 타입;
        car1.drive(); // 변환 후에는 부모 클래스 멤버만 접근 가능
 
        System.out.println("===== 상속 처리 =====");
        FireEngine fe3 = new FireEngine();
        fe3.drive();
        fe3.water();
 
        System.out.println("===== 부모 타입 =====");
        Car car2 = new Car();
        // 자식타입의 참조변수로 부모타입의 인스턴스를 참조할 수는 없다.
        FireEngine fe4 = (FireEngine) car2;
        fe4.drive();
 
    }
}
 
class Car {
    String color;
    int door;
 
    void drive() {
        System.out.println("운전중!");
    }
 
    void stop() {
        System.out.println("정지.");
    }
}
 
class FireEngine extends Car {
    void water() {
        System.out.println("물 뿌리는 중!");
    }
}
 

 

 

예제4)

상위클래스 타입의 변수가 복수의 하위클래스 객체를 참조할 수 있도록 하는 것을 다형성이라 한다.

- Product 가 상위 클래스, Tv, Computer, Audio 는 하위 클래스

import java.util.ArrayList;
 
class Product {
    int price;            // 제품의 가격
    int bonusPoint;    // 제품구매 시 제공하는 보너스점수
 
    public Product(int price) {
        this.price = price;
        this.bonusPoint = (int)(price / 10.0);
    }
 
    public Product() {
    }
}
 
class Tv extends Product {
    public Tv() {
        // 조상 클래스의 생성자 Product(int price)를 호출.
        super(100);
    }
 
    @Override
    public String toString(){
        // Object 클래스의 toString()을 오버라이딩한다.
        return "Tv";
    }
}
 
class Computer extends Product {
    public Computer() {
        // 조상 클래스의 생성자 Product(int price)를 호출.
        super(200);
    }
 
    @Override
    public String toString() {
        // Object 클래스의 toString()을 오버라이딩한다.
        return "Computer";
    }
}
 
class Audio extends Product {
    public Audio() {
        // 조상 클래스의 생성자 Product(int price)를 호출.
        super(50);
    }
 
    @Override
    public String toString() {
        // Object 클래스의 toString()을 오버라이딩한다.
        return "Audio";
    }
}
 
class Buyer {
    int money = 1000;      // 소유금액
    int bonusPoint = 0// 보너스점수
    ArrayList<Product> cart = new ArrayList<>();// 구입한 제품을 저장하기 위한 배열
 
    void buy(Product p) { // 매개 변수가 Product 타입의 참조변수
        if(money < p.price) {
            System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
            return;
        }
 
        money -= p.price;             // 가진 돈에서 구입한 제품의 가격을 뺀다.
        bonusPoint += p.bonusPoint;   // 제품의 보너스 점수를 추가한다.
        cart.add(p);                // 제품을 ArrayList<Product> cart에 추가한다.
        System.out.print(p + "를 구입하셨습니다.");
        System.out.println("현재 잔액은 " + money + " 만원입니다.");
    }
 
    void summary() {                  // 구매한 물품에 대한 정보를 요약해서 보여 준다.
        int sum = 0;                 // 구입한 물품의 가격합계
        String itemList ="";         // 구입한 물품목록
 
        // 반복문을 이용해서 구입한 물품의 총 가격과 목록을 만든다.
        for(int i=0; i < cart.size();i++) {
            sum += cart.get(i).price;
            if(i == cart.size() -1){
                itemList += cart.get(i) + " ";
            } else {
                itemList += cart.get(i) + ", ";
            }
        }
        System.out.println("구입하신 물품의 총 금액은 " + sum + "만원입니다.");
        System.out.println("구입하신 제품은 " + itemList + "입니다.");
    }
}
 
public class Ploymorphism {
    public static void main(String[] args) {
        Buyer b = new Buyer();
 
        b.buy(new Tv());
        b.buy(new Computer());
        b.buy(new Audio());
        b.summary();
    }
}
 

 

 

블로그 이미지

Link2Me

,
728x90

패키지의 선언
-
패키지는 소스파일의 첫 번째 문장으로 단 한번 선언
-
같은 소스 파일의 클래스들은 모두 같은 패키지에 속하게 된다.
-
패키지 선언이 없으면, 이름없는 패키지에 속하게 된다.

- 패키지명은 선언시 반드시 소문자로 선언해야 한다.


import
-
클래스를 사용할 때 패키지 이름을 생략할 수 있다.
-
컴파일러에게 클래스가 속한 패키지를 알려준다.
-
java.lang 패키지의 클래스는 import 하지 않고도 사용할 수 있다.
- import
문은 패키지문과 클래스 선언의 사이에 선언한다.

접근 제어자

제어자

같은 클래스

같은 패키지

자손 클래스

전체

 public





 protected



 

 (default)

 

 

private

 

 

 


아래 코드는 Eclipse 툴이 아니라 Android Studio 에서 Android 어플 작성을 위한 코드 생성으로 테스트했다.

개념은 동일하므로 Eclipse, Android Studio 상관 없다.

package com.link2me.android.pkg1;

public class MyParent { // 접근 제어자가 public
    private int prv; // 같은 클래스
    int dft; // 같은 패키지
    protected int prt; // 같은/다른 패키지 + 자손
    public int pub; // 접근 제한 없음

    public void printMembers() {
        System.out.println(prv); // OK, 같은 클래스
        System.out.println(dft); // OK, 같은 클래스
        System.out.println(prt); // OK, 같은 클래스
        System.out.println(pub); // OK, 같은 클래스
    }
}

class MyParentTest { // 접근제어자가 default
    // 하나의 파일에 public class 이름이 2개 오면 에러가 발생한다.
    // 같은 패키지
    public void printMembers(){
        MyParent mp = new MyParent();
        //System.out.println(mp.prv); // 에러, private은 같은 class 내에서만 접근 가능
        System.out.println(mp.dft); // OK, default는 같은 패키지내에서 접근 가능
        System.out.println(mp.prt); // OK, protected는 같은/다른 패키지 + 자손
        System.out.println(mp.pub); // OK, public은 접근 제한이 전혀 없다
    }

package com.link2me.android.pkg2;

import com.link2me.android.pkg1.MyParent;

public class MyChild extends MyParent { // 상속 받음
    // 다른 패키지 + 자손
    public void printMembers(){
        //System.out.println(prv); // 에러, private은 같은 class 내에서만 접근 가능
        //System.out.println(dft); // 에러, default는 같은 패키지내에서만 접근 가능
        System.out.println(prt); // OK, protected는 같은/다른 패키지 + 자손
        System.out.println(pub); // OK, public은 접근 제한이 전혀 없다
    }
}

class MyParentTest { // 접근제어자가 default
    // 하나의 파일에 public class 이름이 2개 오면 에러가 발생한다.
    // 다른 패키지 + 다른 class
    public void printMembers(){
        MyParent mp = new MyParent();
        //System.out.println(mp.prv); // 에러 private은 같은 class 내에서만 접근 가능
        //System.out.println(mp.dft); // 에러, default는 같은 패키지내에서만 접근 가능
        //System.out.println(mp.prt); // 에러, protected는 같은/다른 패키지 + 자손
        System.out.println(mp.pub); // OK, public은 접근 제한이 전혀 없다
    }
}


블로그 이미지

Link2Me

,
728x90

PHPExcel 이 deprecated 되어 PHP 7.2 이상에서 지원이 안되므로 PHPSpreadsheet 를 설치해야 한다.


이용환경 : CentOS 7


PHP 7.3 yum 설치 스크립트 : https://link2me.tistory.com/1841 참조


설치 방법

- 윈도우 환경에서는 https://getcomposer.org/download/ 에서 설치 파일을 다운로드 하는 거 같다.

- 리눅스 설치 환경에서는 composor 가 설치되어 있어야 한다.

https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_composer_%EC%84%A4%EC%B9%98

에 나온 방법대로 설치를 한다.


먼저 vi /etc/php.ini 에서 allow_url_fopen = On 이 되어 있어야 아래 코드가 동작된다.


curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
composer -V
export COMPOSER_ALLOW_SUPERUSER=1
composer -V
echo "export COMPOSER_ALLOW_SUPERUSER=1" >> ~/.bashrc
cat ~/.bashrc | grep export




이제 PhpSpreadsheet 의 설치는 PHP가 설치된 경로에서 한다.


cd /var/www/html/

composer require phpoffice/phpspreadsheet




실 사용 예제

기존 PHPExcel 라이브러리를 이용하여 사용하던 코드에서 변경할 부분만 변경해서 사용하면 된다.

<?php
error_reporting(0);
ini_set('memory_limit', -1); // 메모리 제한을 해제해준다.

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'; // 세션 체크
require_once $g['path_config'].'config.php';
require_once $g['path_class'].'dbconnect.php';
require_once $g['path_class'].'adminClass.php';
require_once $g['path_admin'].'PHPExcel.php';
$a = new adminClass();

$fname="members";
$posArr=$a->StaffPositionMNArray();

$objPHPExcel = new PHPExcel();
$objPHPExcel->setActiveSheetIndex(0); //set first sheet as active

$objSheet = $objPHPExcel->getActiveSheet();

$objPHPExcel->getDefaultStyle()->getFont()->setSize(12); // 폰트 사이즈
$objSheet->SetCellValue('A2', "번호");
$objSheet->SetCellValue('B2', "성명");
$objSheet->SetCellValue('C2', "직급");
$objSheet->SetCellValue('D2', "아이디");
$objSheet->SetCellValue('E2', "부서1");
$objSheet->SetCellValue('F2', "부서2");
$objSheet->SetCellValue('G2', "부서3");
$objSheet->SetCellValue('H2', "부서4");
$objSheet->SetCellValue('I2', "등록일자");
$objSheet->SetCellValue('J2', "최근접속");
cellColor('A2:J2', 'F28A8C'); // 헤더 배경색 지정

$i=3; // 값을 기록할 셀의 시작위치
$rowCount = 1; // 넘버링

$sql="select * from members ";
$sql.=" order by idx";
$result=mysqli_query($db,$sql);
while($R=mysqli_fetch_array($result)){

    $objSheet->SetCellValue('A'.$i, $rowCount);
    $objSheet->SetCellValue('B'.$i, $R['username']);
    $objSheet->SetCellValue('C'.$i, $posArr[$R['codeID']]);
    $objSheet->SetCellValue('D'.$i, $R['userid']);
    $objSheet->SetCellValue('E'.$i, $R['group2']);
    $objSheet->SetCellValue('F'.$i, $R['group3']);
    $objSheet->SetCellValue('G'.$i, $R['group4']);
    $objSheet->SetCellValue('H'.$i, $R['group5']);
    $objSheet->SetCellValue('I'.$i, substr($R['regdate'],0,10));
    $objSheet->SetCellValue('J'.$i, SETDATE($R['date']));

    $i++;
    $rowCount++;
}

// 표 그리기
$i--;
$objSheet->getStyle('A2:J'.$i)->getBorders()->getAllBorders()->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);

// 헤더 칼럼 가운데 정렬
$objSheet->getStyle('A2:J2')->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);

// 셀 높이
$objSheet->getRowDimension(1)->setRowHeight(20);

// 칼럼 사이즈 자동 조정
$objSheet->getColumnDimension('A')->setWidth(6); // setWidth(18);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('B')->setWidth(8);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('C')->setWidth(10);
$objSheet->getColumnDimension('D')->setWidth(10);
$objSheet->getColumnDimension('E')->setWidth(21);
$objSheet->getColumnDimension('F')->setWidth(19);
$objSheet->getColumnDimension('G')->setWidth(18);
$objSheet->getColumnDimension('H')->setWidth(18);
$objSheet->getColumnDimension('I')->setWidth(11);
$objSheet->getColumnDimension('J')->setWidth(11);

// 파일 PC로 다운로드
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename='.$fname.'.xlsx');
header('Cache-Control: max-age=0');

$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, "Excel2007");
$objWriter->save('php://output');
exit;

// 엑셀의 셀 배경색 지정
function cellColor($cells,$color){
    global $objSheet;

    $objSheet->getStyle($cells)->getFill()->applyFromArray(array(
        'type' => PHPExcel_Style_Fill::FILL_SOLID,
        'startcolor' => array(
             'rgb' => $color
        )
    ));
}

function SETDATE($date){
    $date = preg_replace("/[^0-9]/", "", $date);    // 숫자 이외 제거
    return preg_replace("/([0-9]{4})([0-9]{2})([0-9]{2})$/", "\\1-\\2-\\3", $date);
}

?>
 



이제 PHPSpreadsheet 라이브러리를 이용한 코드 예제를 보면 변경된 부분이 뭔지 알 수 있다.

<?php
error_reporting(0);
ini_set('memory_limit', -1); // 메모리 제한을 해제해준다.

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'; // 세션 체크
require_once $g['path_config'].'config.php';
require_once $g['path_class'].'dbconnect.php';
require_once $g['path_class'].'adminClass.php';
require_once $g['path_root'].'vendor/autoload.php';
$a = new adminClass();

$fname="members";
$posArr=$a->StaffPositionMNArray();

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$objPHPExcel = new Spreadsheet();
$objPHPExcel->setActiveSheetIndex(0); //set first sheet as active

$objSheet = $objPHPExcel->getActiveSheet();

$objPHPExcel->getDefaultStyle()->getFont()->setSize(12); // 폰트 사이즈
$objSheet->SetCellValue('A2', "번호");
$objSheet->SetCellValue('B2', "성명");
$objSheet->SetCellValue('C2', "직급");
$objSheet->SetCellValue('D2', "아이디");
$objSheet->SetCellValue('E2', "부서1");
$objSheet->SetCellValue('F2', "부서2");
$objSheet->SetCellValue('G2', "부서3");
$objSheet->SetCellValue('H2', "부서4");
$objSheet->SetCellValue('I2', "등록일자");
$objSheet->SetCellValue('J2', "최근접속");
// 헤더 배경색 지정
$objSheet->getStyle('A2:J2')->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('F28A8C');

$i=3; // 값을 기록할 셀의 시작위치
$rowCount = 1; // 넘버링

$sql="select * from members ";
$sql.=" order by idx";
$result=mysqli_query($db,$sql);
while($R=mysqli_fetch_array($result)){

    $objSheet->SetCellValue('A'.$i, $rowCount);
    $objSheet->SetCellValue('B'.$i, $R['username']);
    $objSheet->SetCellValue('C'.$i, $posArr[$R['codeID']]);
    $objSheet->SetCellValue('D'.$i, $R['userid']);
    $objSheet->SetCellValue('E'.$i, $R['group2']);
    $objSheet->SetCellValue('F'.$i, $R['group3']);
    $objSheet->SetCellValue('G'.$i, $R['group4']);
    $objSheet->SetCellValue('H'.$i, $R['group5']);
    $objSheet->SetCellValue('I'.$i, substr($R['regdate'],0,10));
    $objSheet->SetCellValue('J'.$i, SETDATE($R['date']));

    $i++;
    $rowCount++;
}

// 표 그리기
$i--;
$objSheet->getStyle('A2:J'.$i)->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);

// 헤더 칼럼 가운데 정렬
$objSheet->getStyle('A2:J2')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);

// 셀 높이
$objSheet->getRowDimension(1)->setRowHeight(20);

// 칼럼 사이즈 자동 조정
$objSheet->getColumnDimension('A')->setWidth(6); // setWidth(18);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('B')->setWidth(8);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('C')->setWidth(10);
$objSheet->getColumnDimension('D')->setWidth(10);
$objSheet->getColumnDimension('E')->setWidth(21);
$objSheet->getColumnDimension('F')->setWidth(19);
$objSheet->getColumnDimension('G')->setWidth(18);
$objSheet->getColumnDimension('H')->setWidth(18);
$objSheet->getColumnDimension('I')->setWidth(11);
$objSheet->getColumnDimension('J')->setWidth(11);

// 파일 PC로 다운로드
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename='.$fname.'.xlsx');
header('Cache-Control: max-age=0');

/* // OLD 버전
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, "Excel2007");
$objWriter->save('php://output');
*/
$writer = new Xlsx($objPHPExcel);
$writer->save('php://output');
exit;

function SETDATE($date){
    $date = preg_replace("/[^0-9]/", "", $date);    // 숫자 이외 제거
    return preg_replace("/([0-9]{4})([0-9]{2})([0-9]{2})$/", "\\1-\\2-\\3", $date);
}

?>
 


블로그 이미지

Link2Me

,
728x90

CentOS 7 에서 Apache 와 PHP 7.3 yum 설치하고 MariaDB 10.3 을 yum 설치하는 스크립트를 적어둔다.


################################
##### MariaDB 10.3 버전 설치 #####
################################

vi /etc/yum.repos.d/MariaDB.repo
# MariaDB 10.3 CentOS repository list - created 2019-01-13 00:47 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.3/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

yum -y install MariaDB-server MariaDB-client

# mariadb 부팅 시 자동 시작 설정
systemctl enable mariadb

# mariadb 시작 (둘 중 하나 실행)
systemctl start mariadb
service mariadb start

# mariadb 상태 확인
service mariadb status

# Maria DB 보안 설정하기
mysql_secure_installation
비밀번호 설정

vi /etc/my.cnf.d/server.cnf
[mysqld]
character-set-server = utf8
collation-server     = utf8_general_ci

service mariadb restart
mysql -u root -p
status
quit

############################
### root 패스워드 분실 복구 ####
############################
# 서비스 정지
systemctl stop mariadb

# mariadb 안전모드 실행
sudo /usr/bin/mysqld_safe --skip-grant &

# 패스워드 변경
mysql -uroot mysql
update user set password=password('변경할비밀번호') where user='root';
flush privileges;
exit;

# 접속 테스트
mysql -uroot -p
비밀번호 입력

# 서비스 재시작
systemctl restart mariadb 

#############################################
######### CentOS 7 버전 확인 ##################
#############################################
cat /etc/redhat-release

## 현재 설치된 PHP 버전 확인
yum list php

yum install expat-devel

## 설치확인
yum list installed | grep httpd
yum list installed | grep php
yum list installed | grep MariaDB

# 설치 확인
rpm -qa | grep httpd
rpm -qa | grep php
rpm -qa | grep MariaDB

## 설치된 것 한꺼번에 지우기
rpm -qa | grep php >list

# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
sudo yum -y remove $(awk '{print $1}' <list)

# 삭제 방법 : yum autoremove [package_name]
yum remove -y vsftpd


# Remi 저장소를 설치하고 활성화한다.
yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm

# yum 저장소와 패키지를 관리
yum -y install epel-release yum-utils

# Disable repo for PHP 5.4
yum-config-manager --disable remi-php54
yum-config-manager --enable remi-php73

# Install PHP 7.3 on CentOS 7
yum install -y --enablerepo=remi-php73 httpd php php-cli php-common php-devel php-ldap php-mbstring php-mcrypt php-mysqlnd
yum install -y --enablerepo=remi-php73 php-pdo php-pgsql php-sqlite php-process php-snmp php-soap php-libxml php-xml php-pear php-gd
yum install -y --enablerepo=remi-php73 php-fpm php-dom php-simplexml php-ssh2 php-xmlreader php-curl php-date php-exif php-filter
yum install -y --enablerepo=remi-php73 php-ftp php-hash php-iconv php-imagick php-json php-openssl php-pcre php-posix php-sockets
yum install -y --enablerepo=remi-php73 php-spl php-tokenizer php-zlib php-gmp php-Icinga php-intl
yum install -y --enablerepo=remi-php73 php-pecl-zip zip unzip php-zip

# PHP 버전 확인
php -v

# Apache 버전 확인
httpd -v

# 서비스 활성화
systemctl enable httpd

# 서비스 시작
systemctl start httpd

systemctl stop httpd
systemctl restart httpd
systemctl status httpd

# openssl 버전 확인
openssl version

## httpd.conf 파일 수정
vi /etc/httpd/conf/httpd.conf
ServerName localhost
User nobody
Group nobody
<IfModule dir_module>
    DirectoryIndex index.html index.php index.jsp
</IfModule>

<Directory "/var/www/html">
    Options IncludesNoExec
    AllowOverride None
    Require all granted
</Directory>


vi /etc/php.ini
short_open_tag = On
post_max_size = 60M  ;// 8M 으로 되어 있었음.
memory_limit = 128M
upload_max_filesize = 50M ;// 기본 2M 으로 되어 있었음

## 보안설정
sudo chmod 640 /etc/httpd/conf/httpd.conf
sudo chown root:root /etc/httpd/conf/httpd.conf
sudo chmod 640 /etc/php.ini
sudo chown root:root /etc/php.ini

sudo chown -R apache:apache /var/www/html
sudo chmod -R 755 /var/www/html

# 포트 활성화 여부 확인
iptables -L

# 열린포트 확인
netstat -tnlp

### 시간 동기화
yum -y install rdate

crontab -e
00 00  * * * /usr/bin/rdate -s time.bora.net && /sbin/clock -w

# mc 설치
yum -y install mc
 


블로그 이미지

Link2Me

,
728x90

개발툴 설치를 기본으로 설치하지 않고 내가 원하는 디렉토리를 지정하여 설치할 수 있다.



이렇게 필요한 것을 설치하면 된다.

블로그 이미지

Link2Me

,
728x90


이번에 구성해 본 건 위 그림의 1번 설정 내역이다.

Apache 서버에서 Reverse Proxy를 설정하여 사설망 PHP-FPM이 설정된 서버로 포워딩을 해서 처리한다.

포트를 9000 번으로 변경해서 테스트를 해보고 싶은데 PHP-FPM 설정 방법을 완벽하게 이해하지 못했다.

설정을 제대로 이해하고 한 것인지는 모르겠는데 동작은 이상없이 잘 된다는 건 확인했다.


#####################################
### [Apache] Reverse Proxy 설정 ###
#####################################

# 설치 확인
rpm -qa | grep httpd

## 설치된 것 한꺼번에 지우기
rpm -qa | grep httpd >list

# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
sudo yum -y remove $(awk '{print $1}' <list)

#Step 1 – Install Apache2
yum -y install httpd

#Step 2 httpd.conf 파일 수정
vi /etc/httpd/conf/httpd.conf
ServerName localhost
User nobody
Group nobody
<IfModule dir_module>
    DirectoryIndex index.html index.php
</IfModule>

<Directory "/var/www/html">
    Options IncludesNoExec
    AllowOverride None
    Order deny,allow
    Allow from all
</Directory>


#Step 3 Virtual Host 설정

# Reverse Proxy : 외부에서 접속했을 때 내부서버나 다른 곳으로 연결해 주는 방법
vi /etc/httpd/conf.d/default-site.conf
<VirtualHost *:80>
    ProxyPreserveHost On

    ProxyPass / http://172.16.100.252:80/
    ProxyPassReverse / http://172.16.100.252:80/
</VirtualHost>

## 보안설정
chmod 640 /etc/httpd/conf/httpd.conf
chown root:root /etc/httpd/conf/httpd.conf

# 포트 활성화 여부 확인
iptables -L

# Apache 서버의 로그 위치
cat /var/log/httpd/error_log

# 서비스 활성화
systemctl enable httpd

# 서비스 시작
systemctl start httpd

systemctl stop httpd
systemctl restart httpd
systemctl status httpd



인터넷망에서 사용할 수 없는 IP 대역이

10.X.X.X

172.16.X.X

192.168.X.X

이다.

사설망 접속은 NAT(Network Address Translation) 처리를 해주는 장치가 있어야 한다.

IP Time 공유기, Router, Switch, 방화벽 등이 이런 기능을 수행한다.



블로그 이미지

Link2Me

,