728x90

안드로이드 View Binding 기능을 어떤 걸로 테스트하면 좋을까 생각하다가 GitHub 에서 계산기를 받아서 그걸로 View Binding 테스트를 했다.


뷰 바인딩은 view와 상호작용하는 코드를 더욱 쉽게 작성할 수 있도록 도와주는 기능이다.


View Binding 을 하는 방법

1. 앱 build.gradle 추가사항

    viewBinding {
        enabled = true
    } 


2. Java code 수정사항

setContentView(R.layout.activity_main); 대신에

private ActivityMainBinding binding;

binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. inflate 호출하여 인스턴스 생성

View view = binding.getRoot(); // 2. getRoot()

setContentView(view); // 3. 화면상의 활성 뷰로 만든다.


binding.btnClear.setOnClickListener(v -> {
    binding.tvInput.setText("");
    binding.tvOutput.setText("");
});


3. XML 파일 수정사항은 없다.

    - XML 파일을 분리하여 호출하는 구조로는 binding 을 인식하지 못하더라. 그래서 1개의 파일로 작성했음.

    - findViewById 호출 대신에 직접 id값을 binding 한다.


<Button
    android:id="@+id/btnClear"
    android:layout_width="wrap_content"
    android:layout_height="100dp"
    android:layout_row="0"
    android:layout_column="0"
    android:backgroundTint="@color/colorWhiteGray"
    android:text="CL"
    android:textColor="@color/colorBlack"
    android:textSize="25sp" />


계산기 예제코드

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    viewBinding {
        enabled = true
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
    implementation 'com.google.android.material:material:1.2.1'
    // Rhino is an open-source implementation of JavaScript written entirely in Java.
    implementation group: 'org.mozilla', name: 'rhino', version: '1.7.13'

}


import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

import com.link2me.android.calculator.databinding.ActivityMainBinding;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

public class MainActivity extends AppCompatActivity {
    String process;
    Boolean checkBracket = false;

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. inflate 호출하여 인스턴스 생성
        View view = binding.getRoot(); // 2. getRoot()
        setContentView(view); // 3. 화면상의 활성 뷰로 만든다.

        binding.btnClear.setOnClickListener(v -> {
            binding.tvInput.setText("");
            binding.tvOutput.setText("");
        });

        binding.btn0.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "0");
        });

        binding.btn1.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "1");
        });

        binding.btn2.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "2");
        });

        binding.btn3.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "3");
        });

        binding.btn4.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "4");
        });

        binding.btn5.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "5");
        });

        binding.btn6.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "6");
        });

        binding.btn7.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "7");
        });

        binding.btn8.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "8");
        });

        binding.btn9.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "9");
        });

        binding.btn0.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "0");
        });

        binding.btnAdd.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "+");
        });

        binding.btnMinus.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "-");
        });

        binding.btnMultiply.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "×");
        });

        binding.btnDivision.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "÷");
        });

        binding.btnDot.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + ".");
        });

        binding.btnPercent.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "%");
        });

        binding.btnEquals.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();

            process = process.replaceAll("×", "*");
            process = process.replaceAll("%",  "/100");
            process = process.replaceAll("÷","/");

            Context rhino = Context.enter();
            rhino.setOptimizationLevel(-1);

            String finalResult = "";

            try {
                Scriptable scriptable = rhino.initStandardObjects();
                finalResult = rhino.evaluateString(scriptable,process,"javascript",1,null).toString();
            }catch (Exception e){
                finalResult="0";
            }
            binding.tvOutput.setText(finalResult);
        });

        binding.btnBracket.setOnClickListener(v -> {
            if (checkBracket){
                process = binding.tvInput.getText().toString();
                binding.tvInput.setText(process + ")");
                checkBracket = false;
            }
            else{
                process = binding.tvInput.getText().toString();
                binding.tvInput.setText(process + "(");
                checkBracket = true;
            }
        });

    }
}


전체 코드는 https://github.com/jsk005/JavaProjects/tree/master/calculator 에 있다.

블로그 이미지

Link2Me

,
728x90

안드로이드 버전이 6.0 이상인 경우 출처를 알 수 없는 앱인 경우 설치 허용 체크가 해제된 상태다.

이 경우 설정을 체크하는 화면으로 이동하는 코드를 테스트 하고 적어둔다.


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


implementation 'gun0912.ted:tedpermission:2.0.0'


출처를 알 수 없는 앱 설정 코드

if(Build.VERSION.SDK_INT >= 26){ // 출처를 알 수 없는 앱 설정 화면 띄우기
    PackageManager pm = LoginActivity.this.getPackageManager(); // 현재 설치하는 앱
    boolean isTrue = pm.canRequestPackageInstalls();
    if (!pm.canRequestPackageInstalls()){ // 신뢰할 수 있는 앱 체크로 선택되어 있지 않다면....
        startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName())));

    }
}

private void NonSecretApp_Setting() {
    if(Build.VERSION.SDK_INT >= 26)  { // 출처를 알 수 없는 앱 설정 화면 띄우기
        PackageManager pm = getPackageManager();
        Log.e("Package Name",pm.getInstalledPackages(0).get(0).packageName);
        Log.e("Package Name",getPackageName());
        if (!pm.canRequestPackageInstalls()) {
            AlertDialog.Builder b = new AlertDialog.Builder(this, android.R.style.Theme_DeviceDefault_Light_Dialog);
            b.setTitle("알림");
            b.setMessage("보안을 위해 스마트폰 환경설정의 '앱 설치 허용'을 설정해 주시기 바랍니다.설정화면으로 이동하시겠습니까?");
            b.setCancelable(false);
            b.setPositiveButton("설정하기", (dialog, which) -> {
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
                intent.setData(Uri.parse("package:" + getPackageName()));
                startActivity(intent);
            });

            b.setNegativeButton("건너띄기", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {

                }
            });
            b.show();
        }
    }
}


한번 신뢰 설정을 하면 두번다시 물어보지는 않으므로 안심하고 사용하면 된다.


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

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

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

private void checkPermissions() {


    if(Build.VERSION.SDK_INT >= 26){ // 출처를 알 수 없는 앱 설정 화면 띄우기
        PackageManager pm = context.getPackageManager();
        Log.e("Package Name",getPackageName());
        boolean isTrue = pm.canRequestPackageInstalls();
        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(context)
                .setPermissionListener(permissionlistener)
                .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                .setPermissions(new String[]{
                        android.Manifest.permission.READ_PHONE_STATE,
                        android.Manifest.permission.READ_CALL_LOG, // 안드로이드 9.0 에서는 이것도 추가하라네
                        android.Manifest.permission.CALL_PHONE,  // 전화걸기 및 관리
                        android.Manifest.permission.ACCESS_FINE_LOCATION,
                        android.Manifest.permission.ACCESS_COARSE_LOCATION
                })
                .check();

    } else {
        initView();
    }
}
 


블로그 이미지

Link2Me

,
728x90

안드로이드 WebView 를 이용하여 MDB 기반 Web 화면을 읽어서 처리하는 코드이다.

LG G5폰(안드로이드 6.0)에서는 잘 동작한다.

삼성 갤럭시 S10(안드로이드 9.0)에서 실행을 시켰더니 로그인 자체부터 막힌다.

구글링해보니 HTTPS 통신이어야만 정상 동작된다고 나온다.

HTTP 통신에서 동작하도록 하는 방법은 아래와 같이 코드를 수정해주면 된다.


AndroidManifest.xml 파일 수정사항

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/AppTheme.NoActionBar">


/res/xml/network_security_config.xml 추가

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>


로 해주거나, 아래와 같이 한줄을 추가해주라고 나온다.

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"


일단 여기까지 하면 로그인까지는 문제없이 잘 동작되더라.

activity_main.xml

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:orientation="vertical"
        >
        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:adjustViewBounds="true"
            android:scaleType="fitStart"
            />

    </LinearLayout>

</LinearLayout>


WebView 는 아래와 같이 처리하면 삼성 갤럭시 S10 에서도 이상없이 동작되는 걸 확인했다.

    private void myWebView(){
        String web_url = getIntent().getExtras().getString("url");
        try {
            url = URLDecoder.decode(web_url,"UTF-8"); // target
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println("web url : " + web_url);

        mWebView = (WebView) findViewById(R.id.webview); // Layout 와의 연결

        mWebSettings = mWebView.getSettings(); // 세부 세팅 등록
        mWebSettings.setJavaScriptEnabled(true); // 자바스크립트 사용 허용
        mWebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        mWebSettings.setBuiltInZoomControls(false); // 화면 확대 축소 허용 여부
        mWebSettings.setDisplayZoomControls(false);
        mWebSettings.setSupportZoom(true); // 줌 사용 여부
        //mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); // 컨텐츠 사이즈 맞추기
        mWebSettings.setUserAgentString("Android");
        if(Build.VERSION.SDK_INT >= 16){
            mWebSettings.setAllowFileAccessFromFileURLs(true);
            mWebSettings.setAllowUniversalAccessFromFileURLs(true);
        }
        if(Build.VERSION.SDK_INT >= 21){
            mWebSettings.setMixedContentMode(mWebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        mWebView.setWebChromeClient(new WebChromeClient());
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.startsWith("tel:")) {
                    Intent call_phone = new Intent(Intent.ACTION_CALL);
                    call_phone.setData(Uri.parse(url));
                    startActivity(call_phone); // 권한 설정은 Loing.java에서 처리했음
                } else if (url.startsWith("sms:")) {
                    Intent i = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
                    startActivity(i);
                } else if (url.startsWith("intent:")) {
                    try {
                        Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
                        Intent existPackage = getPackageManager().getLaunchIntentForPackage(intent.getPackage());
                        if (existPackage != null) {
                            startActivity(intent);
                        } else {
                            Intent marketIntent = new Intent(Intent.ACTION_VIEW);
                            marketIntent.setData(Uri.parse("market://details?id=" + intent.getPackage()));
                            startActivity(marketIntent);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    view.loadUrl(url);
                }
                return true;
            }

        });

        mWebView.loadUrl(url);
    }


내가 사용했던 build.gradle, webview 등 파일 일부를 첨부한다.


WebView.zip



블로그 이미지

Link2Me

,
728x90

안드로이드 팝업 메뉴를 선택적으로 적용해야 할 경우에 대한 코드다.


/res/menu/popup_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/aaa"
        android:title="초기화" />
    <item android:id="@+id/bbb"
        android:title="로그인" />
    <item android:id="@+id/ccc"
        android:title="제온" />
    <item android:id="@+id/ddd"
        android:title="판테온" />
</menu> 


Java 코드에서 구현할 사항

PopupMenu popup = new PopupMenu(MainActivity.this, view);
popup.setOnMenuItemClickListener(MainActivity.this);
popup.inflate(R.menu.popup_menu);
popup.show(); 

PopupMenu popup = new PopupMenu(MainActivity.this, v);
popup.getMenuInflater().inflate(R.menu.popup_menu, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
    public boolean onMenuItemClick(MenuItem item) {
        Toast.makeText(MainActivity.this, "You Clicked : " + item.getTitle(), Toast.LENGTH_SHORT).show();
        return true;
    }
});
popup.show();

PopupMenu popup = new PopupMenu(MainActivity.this, view);
Menu menu = popup.getMenu();
if(layoutMode.equals("0")){
    menu.add(Menu.NONE, R.id.aaa, Menu.NONE, "초기화");
    menu.add(Menu.NONE, R.id.bbb, Menu.NONE, "로그인");
} else if(layoutMode.equals("1")){
    menu.add(Menu.NONE, R.id.ccc, Menu.NONE, "제온");
    menu.add(Menu.NONE, R.id.ddd, Menu.NONE, "판테온");
}
popup.setOnMenuItemClickListener(MainActivity.this);
popup.show();


선택 적용하지 않고, 한꺼번에 나오게 하고자 한다면 popup.inflate(R.menu.popup_menu);

레이아웃 모드에 따라서 선택적으로 팝업메뉴가 나오게 하고 싶을 때는 위와 같이 구현해주면 된다.


팝업 메뉴를 완전 동적으로 추가하는 것은 https://link2me.tistory.com/1366 를 참조하면 된다.


동적 메뉴 생성 코드 예제

public class Menu_Item {
    private String shop_name;
    private String url;

    public Menu_Item() {
    }

    public Menu_Item(String shop_name, String url) {
        this.shop_name = shop_name;
        this.url = url;
    }

    public String getShop_name() {
        return shop_name;
    }

    public void setShop_name(String shop_name) {
        this.shop_name = shop_name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}
 

package com.link2me.android.dnmenu;

public class MainActivity extends AppCompatActivity implements View.OnClickListener  {
    Context context;
    // ArrayList 선언
    ArrayList<Menu_Item> menuItems  = new ArrayList<>();

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

    private void initView() {
        String url = "http://www.abc.com/chart/data.json";
        DataVolley(url);
        addMenuData();
    }

    private void addMenuData(){
        // 안드로이드에서 직접 추가를 하면 신규 사이트가 추가되거나 삭제 또는 변경시
        // 앱을 업그레이드해야만 현행 정보를 이용할 수 있다.
        MenuList("전체","http://www.abc.com/chart/data.json");
        MenuList("서울","http://www.abc.com/chart/data1.json");
        MenuList("경기","http://www.abc.com/chart/data2.json");
    }

    private void MenuList(String shop_name, String url){
        Menu_Item item = new Menu_Item();
        item.setShop_name(shop_name);
        item.setUrl(url);
        menuItems.add(item);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.back_btn:
                finish();
                overridePendingTransition(R.anim.rightin, R.anim.rightout);
                break;
            case R.id.home_btn:
                Intent intent = new Intent(MainActivity.this, Main.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
                break;
            case R.id.btnMenuList:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(this, view);
                Menu menu = popupMenu.getMenu();
                for(int i = 0; i < menuItems.size(); i++){
                    menu.add(Menu.NONE,i,Menu.NONE, menuItems.get(i).getShop_name());
                }
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        int i = item.getItemId();
                        Toast.makeText(context, item.getTitle() + " 선택했습니다", Toast.LENGTH_SHORT).show();
                        DataVolley(menuItems.get(i).getUrl());
                        return true;
                    }
                });
                popupMenu.show();
                break;
            default:
                break;
        }
    }
}
 



블로그 이미지

Link2Me

,
728x90

Handler는 잘 사용하지 않는데, Handler를 이용한 코딩이 필요하여 다시 Handler 개념 살펴보면서 적어둔다.


public class HandlerActivity extends AppCompatActivity {
    Handler mainThreadHandler;

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

        mainThreadHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what == 1){
                }
            }
        };
    }
}

public class HandlerActivity extends AppCompatActivity {
    CHandler mainThreadHandler;

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

        mainThreadHandler = new Handler();
    }
    private class CHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 1){
                //handle message
            }
        }
    }
}


보통의 경우 위와 같이 Handler 클래스를 생성하는 방법으로 사용한다. 하지만 이런 방식의 경우 "The handler class should be static or leak might occur." 라는 메모리 누수 경고 문구가 발생할 수 있다.


Activity가 종료되더라도 Garbage collect 가 실행되지 않게 되고 결국 Message가 Message Queue에 상당 시간동안 남아있게 되어 Memory Leak 의 원인이 된다.


이를 해결할 방법은 static inner class 로 Handler를 정의해주고 WeakReference 를 이용하여 Activity 메소드를 호출하는 코드를 구현한다.


import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {
    Button handlerStartButton;
    Button handlerStopButton;
    Handler mainThreadHandler;
    static TextView handlerExampleTextView;
    static int workValue = 0;
    boolean running = true;

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

        handlerStartButton = findViewById(R.id.btn_start);
        handlerStopButton = findViewById(R.id.btn_stop);
        handlerExampleTextView = findViewById(R.id.tv);

        mainThreadHandler = new MyHandler(this);

        handlerStartButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = true;
                startCount();
            }
        });
        handlerStopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = false;
                workValue = 0;
            }
        });
    }

    private void startCount(){
        // Start a child thread when button is clicked.
        WorkerThread thread = new WorkerThread();
        thread.start();
    }

    private static class MyHandler extends Handler{
        private final WeakReference<HandlerActivity> mActivity;

        private MyHandler(HandlerActivity activity) {
            mActivity = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = mActivity.get();
            if(activity != null){
                if(msg.what == 1){
                    handlerExampleTextView.setText("BackValue:" + workValue);
                }
            }
        }
    }

    private class WorkerThread extends Thread{
        @Override
        public void run() {
            super.run();
            while(running){
                workValue++;  // 작업스레드 값 증가
                try {
                    java.lang.Thread.sleep(1000); // 1000ms(1초)이므로 1초 단위로 실행
                    System.out.println(java.lang.Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Create a message in child thread.
                Message childThreadMessage = new Message();
                childThreadMessage.what = 1;
                childThreadMessage.arg1 = workValue;
                // Put the message in main thread message queue.
                mainThreadHandler.sendMessage(childThreadMessage);
            }

        }
    }
}

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

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

        <Button
            android:id="@+id/btn_start"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:text="시작"/>
        <Button
            android:id="@+id/btn_stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="중지"/>

    </LinearLayout>

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="결과"
        android:textSize="20dp"/>

</LinearLayout>


참조 : https://blog.naver.com/zoomen1004/220053616676


블로그 이미지

Link2Me

,
728x90

Android EditText에서 줄 바꿈을 추가하는 방법에 대해 검색해보니 여러가지 방법이 나오는데

 

<EditText
    android:id="@+id/message"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_weight="3"
    android:inputType="textMultiLine"
    android:hint="your message" />

 

이 한줄만 추가해주고 입력해보니 줄바꿈되면서 입력이 잘 된다.

 

채팅 기능 구현해보려 하다보니 EditText 기능에 대한 세밀한 공부를 하게 된다.

 

<EditText
    android:inputType="textMultiLine" <!-- inputType에 MultiLine을 설정 -->
    android:lines="8" <!-- 화면에 8줄로 보이게한다. -->
    android:minLines="6" <!-- 최소 6줄 -->
    android:maxLines="10" <!-- 최대 10줄 -->
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    android:scrollbars="vertical" <!-- 수직 스크롤 바  설정 -->
/>

 

 

관련 참조 게시글

https://code.i-harness.com/ko-kr/q/40999a

 

 

블로그 이미지

Link2Me

,
728x90

Android RecyclerView TextView 에 보여줄 내용에 자동으로 Link를 거는 방법을 찾아봤다.


간단하게

<TextView
    android:id="@+id/tv_chatMsg"
    android:text="Hello"
    android:textSize="14dp"
    android:autoLink="phone|web|email|map"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:layout_alignParentTop="true"
    android:background="@drawable/chat_from"
    />


한줄을 추가해주면 되더라.


하이퍼링크 색상은 지정하지 않으면 빨간색인데 별도 색으로 지정하고 싶으면 android:textColorLink="@color/blue" 를 해주면 된다.


텍스트를 클립보드로 복사하는 기능을 추가하고 싶다면

<TextView
    android:id="@+id/tv_chatMsg"
    android:text="Hello"
    android:textSize="14dp"
    android:textColorLink="@color/blue"
    android:textIsSelectable="true"
    android:autoLink="all"
    android:layout_marginTop="3dp"
    android:layout_below="@id/tv_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />

이 한줄을 추가해 주면 된다. 이 속성은 API Level 11부터 지원된다.

자바 코드에서는 msg.setTextIsSelectable(true); 로 설정해주면 된다.
xml 에서 android:textIsSelectable="true" 를 추가하거나 자바 코드에서 추가하거나 둘 중 하나면 하면 된다.


특정 단어를 눌렀을 때 URL 이 동작되도록 하고 싶은 경우에는 이 방법으로는 해결이 안된다.

특정 단어를 눌렀을 때는 http://gun0912.tistory.com/66 박상권님 블로그 참조.


전화번호를 눌렀을 때 바로 전화를 거는 게 아니라 문자를 보낼 수도 있고 복사를 하고 싶은 경우도 있다면....

https://blog.uncommon.is/a-better-way-to-handle-links-in-textview-27bb70b2d31c 참조


msg = (TextView) itemView.findViewById(R.id.tv_chatMsg);
msg.setMovementMethod(BetterLinkMovementMethod.getInstance());
Linkify.addLinks(msg,Linkify.ALL);

로 해봤지만 단순 autoLink 와 변화된 것은 없다.

블로그 이미지

Link2Me

,
728x90

안드로이드 Notification Helper 자료를 검색하고 내용을 보강하고 적어둔다.

안드로이드 8.0 이상(targetSdkVersion 26 이상)에서도 동작되는 코드다.

Noti 메시지를 띄우는createNotification(String title, String message) 과 클릭하면 cancelNotification(Context ctx, int notifyId) 되는 걸 테스트했다.

핵심은 NotificationHelper Class 만드는 것이고 나머지는 간단하게 사용해본 것이다.


 public class NotificationHelper {
    private Context mContext;
    private NotificationManager notificationManager;
    public static final String NOTIFICATION_CHANNEL_ID = "10001";

    public NotificationHelper(Context context) {
        mContext = context;
    }

    /**
     * Create and push the notification
     */
    public void createNotification(String title, String message) {
        Intent resultIntent = new Intent(mContext , ShowNotification.class);
        resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent resultPendingIntent = PendingIntent.getActivity(mContext,
                0 /* Request code */, resultIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mContext,NOTIFICATION_CHANNEL_ID);
        mBuilder.setSmallIcon(R.mipmap.ic_launcher);
        mBuilder.setContentTitle(title)
                .setContentText(message)
                .setAutoCancel(false) //클릭하게 되면 사라지도록...
                .setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
                .setVibrate(new long[] { 1000, 1000, 1000 }) //노티가 등록될 때 진동 패턴
                .setContentIntent(resultPendingIntent);

        notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){ // 8.0 이상
            int importance = NotificationManager.IMPORTANCE_HIGH;
            String channelName = "NOTIFICATION_CHANNEL_NAME";
            NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance);
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.RED);
            notificationChannel.enableVibration(true);
            notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            assert notificationManager != null;
            mBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
            notificationManager.createNotificationChannel(notificationChannel);
        }
        assert notificationManager != null;
        notificationManager.notify(0 /* Request Code */, mBuilder.build());
    }

    public void callSettingNotification(){
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
            intent.putExtra(Settings.EXTRA_APP_PACKAGE,mContext.getPackageName());
            intent.putExtra(Settings.EXTRA_CHANNEL_ID,NOTIFICATION_CHANNEL_ID);
            mContext.startActivity(intent);
        }
    }

    public void cancelNotification(Context ctx, int notifyId) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
            NotificationManager mNotificationManager =
                    (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
            mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
        } else {
            NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.cancel(notifyId);
        }
    }
}


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

notificationmanager.zip


PendingIntent

PendingIntent는 Intent를 포함하는 인텐트로, 목적은 본인 앱이 아닌 외부 앱(Notification, Alarm 등)에게 권한을 줘서 본인 앱을 실행할 수 있도록 허락하는 것이다.


A PendingIntent is a token that you give to a foreign application (e.g. NotificationManager, AlarmManager, Home Screen AppWidgetManager, or other 3rd party applications), which allows the foreign application to use your application's permissions to execute a predefined piece of code.


pending : 미결의, ~를 기다리는 동안


Intent의 기본 개념은 특정 컴포넌트(Activity, Service, Broadcast Receiver, Content Provider)를 실행시키는 메시지라는 것이다.

Intent intent = new Intent(MainActivity.this, NewActivity.class);

startActivity(intent);

그런데 PendingIntent는 생성자가 없고 다음 세 개의 메소드들에 의해서 객체가 생성된다.
ㆍgetActivity(Context, int, Intent, int)
ㆍgetBroadcast(Context, int, Intent, int)
ㆍgetService(Context, int, Intent, int)

- 액티비티를 시작할 인텐트를 생성하기 위해선 PendingIntent.getActivity()를 사용한다.
- 서비스를 시작할 인텐트를 생성하기 위해서는 PendingIntent.getService()를 사용한다.
- BroadcastReceiver를 시작할 인텐트를 생성하기 위해서는 PendingIntent.getBroadcast()를 사용한다.


Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendIntent = PendingIntent.getActivity(context, 0 , intent, PendingIntent.FLAG_UPDATE_CURRENT);

- 정상적인 Activity lifecycle을 태우기 위해선, PendingIntent 생성시 requestCode를 반드시 넣어야한다.(대부분 default값으로 0을 넣는다)

- FLAG_CANCEL_CURRENT : 이전에 생성한 PendingIntent는 취소하고 새로 만든다.
- FLAG_IMMUTABLE : 생성된 PendingIntent 는 수정 불가능하도록 한다.
- FLAG_NO_CREATE : 생성된 PendingIntent 를 반환한다.(재사용 가능), 이미 생성된 PendingIntent가 없다면 null를 return 한다.
- FLAG_ONE_SHOT : 해당 Flag로 생성한 PendingIntent 는 일회성이다.
- FLAG_UPDATE_CURRENT : 이미 생성된 PendingIntent가 존재하면 해당 Intent의 Extra Data만 변경한다.


알람 확인 및 취소 게시글 : https://migom.tistory.com/10





참고사이트

https://medium.com/cr8resume/notification-in-android-8-0-oreo-implementing-notification-channels-d65b0f81ca50


https://www.androidauthority.com/android-8-0-oreo-app-implementing-notification-channels-801097/


http://gun0912.tistory.com/77

블로그 이미지

Link2Me

,
728x90

마시멜로 버전부터는 권한을 일반 권한(Normal Permission)과 위험 권한(Dangerous Permission)으로 나누었으며, 위험 권한의 경우에는 앱이 실행될 때 사용자에게 권한 부여할 것인지 물어보도록 변경되었다.

대표적인 위험권한은 위치, 카메라, 마이크, 연락처, 전화, 문자, 일정, 센서 등이다.


TedPermission 라이브러리를 이용하여 권한체크 하는 방법으로 간단히 이용할 수 있어 편하다.

제작자 블로그 : http://gun0912.tistory.com/


신규 스마트폰은 SDK 버전이 23 이라고 봐도 무방하다.


targetSdkVersion 23 이상으로 설정한 경우
dependencies {
    implementation 'gun0912.ted:tedpermission:2.0.0'
}
추가한다.


사용 예제

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

public class MainActivity extends AppCompatActivity {
    Context context;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView(); // 권한이 승인되었을 때 실행할 함수
        }

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

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

        checkPermissions();
    }

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

        } else {
            initView(); // 권한 승인이 필요없을 때 실행할 함수
        }
    }

    private void initView() {
        imageView = (ImageView) findViewById(R.id.croppedImageView);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {                

            }
        });
    }

}


필요한 권한을 멀티로 지정해주면 된다.

'안드로이드 > Android 기능' 카테고리의 다른 글

Android – TextView autoLink attribute  (0) 2018.09.21
Android Notification  (0) 2018.08.24
Android View  (1) 2018.03.20
WindowManager 주요 Flag  (0) 2018.02.24
Transparent Activity floating/draggable on Homscreen  (0) 2018.01.30
블로그 이미지

Link2Me

,
728x90
View

View Attribute

  • Add TextField
    • android:id
      • view를 구분하는 유일한 값의 id
    • @
      • xml에서 제공받은 Resource라는 표시.
    • +
      • resource의 type을 선언하기전에 사용하는 사용
    • android:layout_width, android:layout_height
      • view의 size를 나타낸다.
      • wrap_content
        • 실제 사용되는 size
      • match_parent
        • 부모의 사이즈.
    • android:hint
      • text 의 값이 아무값도 없을 때 표시해주는 Default 값을 말한다.

출처 : https://blog.naver.com/wmi1258/220172002074


위 블로그는 간단한 개념 설명이 비교적 잘 되어 있어서 다시 찾아보면 좋을 거 같다.

블로그 이미지

Link2Me

,
728x90

win.addFlags(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);

WindowManager를 사용하면, Activity의 제약사항을 뛰어넘어서 개발할 수 있다.
- Context별 사용법을 정확히 알아야한다.
- 메모리 관리를 잘 해야 한다.
- 잠금 해제는 Activity Context 만 가능하다.


팝업창 구현이 쉽지 않다. 최상위 팝업 구현을 하려고 하는데 전화가 걸려오면 최상위로 팝업되는 경우도 있고 뒤로 숨는 경우도 있다.

그래서 블로드, 사이트 등에서 기능을 찾아 정리해보고 있지만, 완벽한 해결이 되지 못했다.


https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html


FLAG_DISMISS_KEYGUARD
window가 attach되면 잠금해제를 하는 flag.
Activity Context를 가진 윈도우매니저에서만 동작

FLAG_LAYOUT_NO_LIMITS

allow window to extend outside of the screen.

window가 화면을 벗어나서도 배치될 수 있다.
숨기거나 일부만 보이는 윈도우를 만들때 사용

FLAG_LAYOUT_IN_SCREEN

place the window within the entire screen, ignoring decorations around the border (such as the status bar).

window가 보여지는 화면의 전체 영역을 가진다.
status bar 혹은 navigation bar 영역까지도 배치될 수 있다.

FLAG_NOT_FOCUSABLE

this window won't ever get key input focus, so the user can not send key or other button events to it.

해당 윈도우가 input focus를 받지 않게 된다.
즉 home button 이나 back button처럼 focus를 받아야 동작하는 것들을 다음 우선순위 window에 넘긴다.

FLAG_NOT_TOUCH_MODAL

even when this window is focusable (its FLAG_NOT_FOCUSABLE is not set), allow any pointer events outside of the window to be sent to the windows behind it.

해당 윈도우가 터치를 받지 않게 된다.
터치가 오면 다음 우선순위 window에 넘긴다.


FLAG_KEEP_SCREEN_ON
as long as this window is visible to the user, keep the device's screen turned on and bright.


FLAG_WATCH_OUTSIDE_TOUCH
if you have set FLAG_NOT_TOUCH_MODAL, you can set this flag to receive a single special MotionEvent with the action MotionEvent.ACTION_OUTSIDE for touches that occur outside of your window.


FLAG_SHOW_WHEN_LOCKED
This constant was deprecated in API level 27. Use showWhenLocked or setShowWhenLocked(boolean) instead to prevent an unintentional double life-cycle event.


FLAG_TURN_SCREEN_ON
This constant was deprecated in API level 27. Use turnScreenOn or setTurnScreenOn(boolean) instead to prevent an unintentional double life-cycle event.



//화면 켜놓기 유지(단말이 sleep 단계로 들어가지 않게 설정)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

//화면 켜놓기 안유지
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);


// Title Bar 없는 상태로 만들기
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                                WindowManager.LayoutParams.FLAG_FULLSCREEN);

// Java Code에서 Status Bar와 Title Bar 모두 없는 상태 만들기
requestWindowFeature(Window.FEATURE_NO_TITLE);

// AndroidManifest.xml 에서 Activity의 Theme를 위와 같이 설정해주면 Title Bar가 모두 없는 상태가 된다.
<activity android:name=".MyActivity"
 android:theme="@android:style/Theme.NoTitleBar">

참고 사이트

https://github.com/inez/CustomIncomingCallScreen




블로그 이미지

Link2Me

,
728x90

전화가 걸려왔을 때 움직이는 팝업창으로 만드는 방법을 찾다가 구글링에서 찾은 것으로 테스트한 걸  적어둔다.

테스트 환경 : Android Studio 3.3


일반적인 코드

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


Draggable Code

View mPopupView;
WindowManager.LayoutParams mParams;
WindowManager mWindowManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE); // 타이틀바 없애기

    mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    mPopupView = getLayoutInflater().inflate(R.layout.activity_callingincoming, null);

    Display display = mWindowManager.getDefaultDisplay();
    @SuppressWarnings("deprecation")
    int width = (int) (display.getWidth() * 0.9); //Display 사이즈의 90%

    mParams = new WindowManager.LayoutParams(
            width,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_TOAST,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
            PixelFormat.TRANSLUCENT);

    mParams.gravity = Gravity.CENTER | Gravity.START;
    mParams.x = 0; // initial position
    mParams.y = 0; // initial position

    mPopupView.setOnTouchListener(new View.OnTouchListener() {
        private int initialX;
        private int initialY;
        private float initialTouchX;
        private float initialTouchY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    initialX = mParams.x;
                    initialY = mParams.y;
                    initialTouchX = event.getRawX();
                    initialTouchY = event.getRawY();
                    return true;
                case MotionEvent.ACTION_UP:
                    return true;
                case MotionEvent.ACTION_MOVE:
                    int newX = initialX
                            + (int) (event.getRawX() - initialTouchX); // new position
                    int newY = initialY
                            + (int) (event.getRawY() - initialTouchY); // new position
                    mParams.x = newX;
                    mParams.y = newY;
                    mWindowManager.updateViewLayout(mPopupView, mParams);
                    return true;
            }
            return false;
        }
    });

    isPopupChk();
    mWindowManager.addView(mPopupView, mParams);
}

public void isPopupChk(){ // 팝업창이 떠있는지 검사
    if (mPopupView.getParent() != null) {
        mWindowManager.removeView(mPopupView);
    }
}


https://stackoverflow.com/questions/10373078/transparent-activity-floating-draggable-on-homscreen

에 나온 Answer 를 참조해서 구현 성공했다.


테스트시 기본개념이 약해서 어려움을 겪었던 부분은

WindowManager.LayoutParams.WRAP_CONTENT,

WindowManager.LayoutParams.WRAP_CONTENT,

부분이었다.

폰 사이즈가 각각 다르므로 폰 사이즈가 달라도 화면에 보여주는 결과는 일정한 비율로 보여주도록 처리해야 하므로 이 부분을

WindowManager.LayoutParams.MATCH_PARENT,

WindowManager.LayoutParams.WRAP_CONTENT,

로 변경하고 일정한 비율로 Linearlayout 을 구현하고나서 원하는 결과를 얻었다.


추가 확인 사항

- 삼성 갤럭시 노트 8(안드로이드 7.1.1)에서 팝업창 방식으로 activity 를 동작시키니까 창이 1~2초후 사라진다.

- 삼성 갤럭시 S7, S8 (안드로이드 7.0) 에서는 팝업창 방식(service, activity) 모두 정상 동작한다.

- 삼성 갤럭시 노트 8 에서 Main UI 방식으로 동작시키니까 팝업창 오랫동안 지속된다.

블로그 이미지

Link2Me

,
728x90

안드로이드 어플에서 이미지가 수평으로 일정시간후 자동으로 화면이 회전되는 CarouselView 를 검색하여 https://github.com/sayyam/carouselview 이 있는 걸 다운로드 해서 테스트해보고 적어둔다.


위 예제는 Web에 올려진 이미지를 Picasso 이미지 라이브러리를 이용하여 보여주는 것과 Drawable 폴더에 있는 이미지를 보여주는 것을 하나의 화면에서 보여주고 있다.


이미지 라이브러리에 대한 자세한 설명은 http://gun0912.tistory.com/17  와 http://gun0912.tistory.com/19 를 참조하면 도움이 많이 된다.


Web 에서 가져온 이미지의 경우 잔상이 좀 남아서 약간 눈에 거슬려보이기는 하지만 기능은 잘 동작된다.

CarouselView 라이브러리가 기능적으로 편리하게 사용하도록 되어 있는거 같다.


아래 내용은 이해하기 쉽게 용어를 변경하고 기능을 테스트했다.

이미지 롤링 보여주기 용도로는 편하고 좋은거 같은데 달력을 carousel 하기에는 적합하지 않은 거 같아서 다른 걸 추가로 테스트 해보려고 한다.


앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "25.0.0"

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

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

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

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.synnapps:carouselview:0.0.9'
    compile 'com.squareup.picasso:picasso:2.5.2'  // 이미지 라이브러리
    compile 'com.android.support:recyclerview-v7:23.2.1' // ListView 개선 버전
    compile "com.android.support:cardview-v7:23.2.1"

}


AndroidManifest.xml 에 추가할 사항

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


Layout xml


 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center">

    <ImageView
        android:id="@+id/fruitImageView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/labelTextView"
        style="@style/TextView.Label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>



MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;
import com.synnapps.carouselview.CarouselView;
import com.synnapps.carouselview.ImageListener;
import com.synnapps.carouselview.ViewListener;

public class MainActivity extends AppCompatActivity {
    CarouselView localCarouselView;
    TextView localCarouselLabel;
    String[] TitleNames = {"Orange", "Grapes", "Strawberry", "Cherry", "Apricot"};
    int[] localImages = {R.drawable.image_1,
                          R.drawable.image_2,
                          R.drawable.image_3,
                          R.drawable.image_4,
                          R.drawable.image_5
    };

    CarouselView webCarouselView;
    TextView webCarouselLabel;
    String[] webImageURLs = {
            Value.IPADDRESS + "/img_01.jpg",
            Value.IPADDRESS + "/img_02.jpg",
            Value.IPADDRESS + "/img_03.jpg",
            Value.IPADDRESS + "/img_04.jpg",
            Value.IPADDRESS + "/img_05.jpg"
    };

    Button pauseButton;

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

        initView();
    }

    private void initView(){

        webCarouselLabel = (TextView) findViewById(R.id.webCarouselLabel);
        // Web 서버 URL 이미지 롤링배너로 보여주기
        webCarouselView = (CarouselView) findViewById(R.id.webCarouselView);
        webCarouselView.setPageCount(webImageURLs.length);
        webCarouselView.setImageListener(new ImageListener() {
            @Override
            public void setImageForPosition(int position, ImageView imageView) {
                Picasso.with(getApplicationContext())
                        .load(webImageURLs[position])
                        .placeholder(localImages[0])
                        .error(localImages[3])
                        .fit().centerCrop()
                        .into(imageView);
            }
        });
        webCarouselView.setIndicatorGravity(Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM);

        localCarouselLabel = (TextView) findViewById(R.id.localCarouselLabel);
        // drawable 이미지 롤링배너 보여주기
        localCarouselView = (CarouselView) findViewById(R.id.localCarouselView);
        localCarouselView.setPageCount(localImages.length);
        localCarouselView.setSlideInterval(4000);
        localCarouselView.setViewListener(new ViewListener() {
            @Override
            public View setViewForPosition(int position) {
                View customView = getLayoutInflater().inflate(R.layout.view_custom, null);

                TextView labelTextView = (TextView) customView.findViewById(R.id.labelTextView);
                ImageView fruitImageView = (ImageView) customView.findViewById(R.id.fruitImageView);

                fruitImageView.setImageResource(localImages[position]);
                labelTextView.setText(TitleNames[position]);

                localCarouselView.setIndicatorGravity(Gravity.CENTER_HORIZONTAL|Gravity.TOP);

                return customView;
            }
        });

        pauseButton = (Button) findViewById(R.id.pauseButton);
        pauseButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                webCarouselView.pauseCarousel();
                localCarouselView.reSetSlideInterval(0);
            }
        });
    }
}


위 테스트에 사용한 소스코드중에서 필요한 것만 발췌했다.


carouselview_ex1.zip



블로그 이미지

Link2Me

,
728x90

단순히 String값이 비어있는지를 체크하고싶은 경우는 TextUtils을 이용하면 좋다.
TextUtils.isEmpty(string)
TextUtils.isEmpty() 를 클릭해서 메서드를 확인해보면 아래와 같다.
public static boolean isEmpty(@Nullable CharSequence str) {
    if (str == null || str.length() == 0)
        return true;
    else
        return false;
}

// 사용법
if(TextUtils.isEmpty(string)){
    // 정말 string 이 비어있으면 true 를 반환
    doSomething();
}


EditText et;
et = (EditText) findViewById(R.id.et01);
// EditText 내용을 가져오기
String getEdit = et.getText().toString();
if(TextUtils.isEmpty(getEdit)) {
    Toast.makeText(MainActivity.this, "값을 입력하세요." Toast.LENGTH_SHORT).show();
}




블로그 이미지

Link2Me

,
728x90

Android SearchView 에서 숫자검색, 초성검색, 자동검색 완성 기능 추가된 코드다.


   // Filter Class
    public void filter(String charText) {
        charText = charText.toLowerCase(Locale.getDefault());
        lvItemList.clear();
        if (charText.length() == 0) {
            lvItemList.addAll(addressItemList);
        } else {
            for (Address_Item wp : addressItemList) {
                if(Utils.isNumber(charText)){ // 숫자여부 체크
                    if(wp.getMobileNO().contains(charText) || wp.getOfficeNO().contains(charText)){
                        // 휴대폰번호 또는 사무실번호에 숫자가 포함되어 있으면
                        lvItemList.add(wp);
                    }
                } else {
                    String iniName = HangulUtils.getHangulInitialSound(wp.getUserNM(), charText);
                    if (iniName.indexOf(charText) >= 0) { // 초성검색어가 있으면 해당 데이터 리스트에 추가
                        lvItemList.add(wp);
                    } else if (wp.getUserNM().toLowerCase(Locale.getDefault()).contains(charText)) {
                        lvItemList.add(wp);
                    }
                }
            }
        }
        notifyDataSetChanged();
    }

 public class Utils {
    public static void CopyStream(InputStream is, OutputStream os)
    {
        final int buffer_size=1024;
        try
        {
            byte[] bytes=new byte[buffer_size];
            for(;;)
            {
                int count=is.read(bytes, 0, buffer_size);
                if(count==-1)
                    break;
                os.write(bytes, 0, count);
            }
        }
        catch(Exception ex){}
    }

    // 숫자인지 여부 체크
    public static boolean isNumber(String str){
        try {
            Double.parseDouble(str) ;
            return true;
        } catch(Exception e){
            return false;
        }
    }

}



블로그 이미지

Link2Me

,
728x90

SearchView 기능을 추가하고 났더니, SearchView 포커스가 자동으로 키 입력을 기다리고 있어서 영 불편하다.

activity_main.xml 파일에서 아래 빨간색으로 표시된 부분을 추가하면 자동으로 포커스가 잡히는 것이 해제된다.


 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <SearchView
        android:id="@+id/search"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:iconifiedByDefault="false"
        android:focusable="false"
        android:focusableInTouchMode="false">

        <requestFocus />
    </SearchView>

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

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

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

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

    </RelativeLayout>

    <ListView
        android:id="@+id/listview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@+id/search" />

</LinearLayout>


블로그 이미지

Link2Me

,
728x90

http://link2me.tistory.com/1377 (Android SearchView) 에서 초성검색 기능을 추가했다.

지인에게 보여드렸더니 초성검색 기능도 제공되면 좋겠다고 해서 구글링으로 초성검색을 검색하여 추가해봤다.

http://milkissboy.tistory.com/32 에 나온 코드를 복사해서 Class 를 하나 추가했다.

그리고 http://link2me.tistory.com/1377 에서 수정한 부분만 여기에 기록해둔다.


HangulUtils.java


초성검색되는 코드는 위 참조한 블로그에 있는 것 중에서 main 메소드 부분만 제거하고 나머지는 그대로 이용


수정 코드

 초성검색 없는 코드

        // Filter Class
        public void filter(String charText) {
            charText = charText.toLowerCase(Locale.getDefault());
            lvItemList.clear();
            if (charText.length() == 0) {
                lvItemList.addAll(addressItemList);
            } else {
                for (Address_Item wp : addressItemList) {
                    if (wp.getUserNM().toLowerCase(Locale.getDefault()).contains(charText)) {
                        lvItemList.add(wp);
                    }
                }
            }
            notifyDataSetChanged();
        }

초성검색 추가 코드

        // Filter Class
        public void filter(String charText) {
            charText = charText.toLowerCase(Locale.getDefault());
            lvItemList.clear();
            if (charText.length() == 0) {
                lvItemList.addAll(addressItemList);
            } else {
                for (Address_Item wp : addressItemList) {
                    String iniName = HangulUtils.getHangulInitialSound(wp.getUserNM(), charText);
                    if (iniName.indexOf(charText) >= 0) { // 초성검색어가 있으면 해당 데이터 리스트에 추가
                        lvItemList.add(wp);
                    } else if (wp.getUserNM().toLowerCase(Locale.getDefault()).contains(charText)) {
                        lvItemList.add(wp);
                    }
                }
            }
            notifyDataSetChanged();
        } 


초성검색어 : ㄱㅂ, ㅇㅈ민, 이ㅈㅇ 등 검색어로 이용 가능


블로그 이미지

Link2Me

,
728x90

Android Studio SearchView 기능을 이용하면 검색처리가 좀 더 편하다.


검색어 자동완성같은 기능을 구현하기 위한 방법이라고 봐도 된다.

먼저, http://abhiandroid.com/ui/searchview 게시글을 다운로드 해서 본인 PC에서 직접 테스트한다.

다운로드 하려면 성명과 E-Mail 을 입력하고 나면 Dropbox 에 올려진 파일을 다운로드 할 수 있다.


아래 내용은 서버에 있는 자료를 ArrayList 에 저장하고 저장된 자료를 기반으로 SearchView 구현에 필요한 것만 발췌한 것이다.


앱 build.gradle 설정 내용 예시




MainActivity.java 주요 내용

변수선언

    ListView listView;
    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 원본 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    ListViewAdapter listViewAdapter;
    SearchView editsearch; 

 public void onCreate(Bundle savedInstanceState)

        listView = (ListView) findViewById(R.id.listview);
        listViewAdapter = new ListViewAdapter(this, searchItemList);
        listView.setAdapter(listViewAdapter); // Binds the Adapter to the ListView
        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);


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

        // Locate the EditText in listview_main.xml
        editsearch = (SearchView) findViewById(R.id.search);
        editsearch.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                // 문자열 입력을 완료했을 때 문자열 반환
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                // 문자열이 변할 때마다 바로바로 문자열 반환
                String text = newText;
                listViewAdapter.filter(text);
                return false;
            }
        });

     private void getServerData() {
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("idx", settings.getString("idx",""));
        String postParams = builder.build().getEncodedQuery();
        new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
    }


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

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

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


    // 아이템 데이터 추가를 위한 메소드
    public void getServerDataList(String photo_image, String uid, String name, String mobileNO, String officeNO, boolean checkItem_flag) {
        Address_Item item = new Address_Item();
        item.setPhoto(photo_image);
        item.setIdx(uid);
        item.setUserNM(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);
        item.setCheckBoxState(checkItem_flag);

        addressItemList.add(item);
    }

    // 선택한 데이터 추가를 위한 메소드
    public void selectDataList(String photo_image, String uid, String name, String mobileNO, String officeNO, boolean checkItem_flag) {
        Address_Item item = new Address_Item();
        item.setPhoto(photo_image);
        item.setIdx(uid);
        item.setUserNM(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);
        item.setCheckBoxState(checkItem_flag);

        searchItemList.add(item);
    }

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

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

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

                // 서버에서 가져온 데이터 저장 (원본 보관용과 검색용 사용 목적)
                getServerDataList(Photo_Image, uid, name, mobileNO, officeNO,false);
                selectDataList(Photo_Image, uid, name, mobileNO, officeNO,false);
            }

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

    }

 ListViewAdapter 주요 부분
    private class ListViewAdapter extends BaseAdapter {
        ImageLoader imageLoader;
        Context context;
        LayoutInflater inflater;
        private List<Address_Item> lvItemList = null;

        public ListViewAdapter(Context context, List<Address_Item> items) {
            this.context = context;
            imageLoader = new ImageLoader(context);
            lvItemList = items;
        }

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

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

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

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

        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();
            final Integer index = Integer.valueOf(position);

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

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

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

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

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

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

            if(addressItem.getPhoto().equals("")){
                final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.drawable.photo_base, 160);
                viewHolder.photo_Image.setImageBitmap(Base_Profile);
            } else {
                String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getPhoto();
                imageLoader.DisplayImage(photoURL, viewHolder.photo_Image);
            }

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

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

            if (isMSG == false) {
                viewHolder.child_btn.setVisibility(View.VISIBLE);
                viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        builder.show();
                    }
                });
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ===" + addressItem.getIdx(), Toast.LENGTH_SHORT).show();
                    }
                });
            } else {
                if (isMSG == true) {
                    viewHolder.child_btn.setVisibility(View.GONE);
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.

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

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

                }
            }

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

            return convertView;
        }

        // Filter Class
        public void filter(String charText) {
            charText = charText.toLowerCase(Locale.getDefault());
            lvItemList.clear();
            if (charText.length() == 0) {
                lvItemList.addAll(addressItemList);
            } else {
                for (Address_Item wp : addressItemList) {
                    if (wp.getUserNM().toLowerCase(Locale.getDefault()).contains(charText)) {
                        lvItemList.add(wp);
                    }
                }
            }
            notifyDataSetChanged();
        }

    }


foreach 문의 기본적인 내용은 http://link2me.tistory.com/1204 참조하면 된다.

블로그 이미지

Link2Me

,
728x90

배터리 교체 등으로 스마트폰의 전원을 OFF 후 부팅시 특정 앱이 자동으로 실행되도록 하는 코드다.


AndroidManifest.xml 파일에 추가할 사항

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver
    android:name=".AutoRun"
    android:enabled="true"
    android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>


android:enabled="true" : 시스템이 알아서 AutoRun Receiver를 실행한다.
android:exported="false" : 외부 어플리케이션에서는 사용할 수 없다.


AutoRun 브로드캐스트 리시버를 생성한다.

Intent intent = new Intent(context, Intro.class); // 부팅후 자동실행할 Activity 는 Intro.java 파일


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class AutoRun extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // 배터리 교체 등으로 부팅시 앱 자동실행 시키기
        String action = intent.getAction();
        if(action.equals("android.intent.action.BOOT_COMPLETED")){
            Intent ii = new Intent(context, Intro.class);
           
ii.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(
ii);
        }
    }
}
 




블로그 이미지

Link2Me

,
728x90

안드로이드 6.0 버전 이상부터 권한 체크 기능을 선택할 수 있도록 만들었다.

앱에서 해당 권한이 필요할때마다 사용자로부터 권한을 허가받도록 변경되었다.

사용자가 권한을 허가했더라도 사용자는 설정화면(설정 > 애플리케이션 > 앱이름 > 권한)을 통해 언제든지 권한을 허용/거부 할 수 있다.


테드 퍼미션 설정 방법 코드 : https://link2me.tistory.com/1509

아래 코드로 해도 되지만 테드 퍼미션 사용하고 부터는 이걸로 활용하고 있다.


개별로 설정해서 사용하다가 다른 자료를 찾다가 코드에 포함된 걸 발견하고 테스트해보니 아주 잘된다.

이 코드만 활용하면 퍼미션 지정때문에 골치아플 일은 없을 것이다.


1. 먼저 알아야 할 사항은 AndroidManifest.xml 에 권한 옵션이 추가되어 있어야 한다.

    아래 코드가 전부 필요한 것이 아니다. 필요한 것만 이용하면 된다.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- 전화걸려올경우 상대방 정보 확인 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.MODIFY_PHONE_READ" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />


2. java 코드에서 멀티 퍼미션 설정코드

    멀티퍼미션에 항목이 6개 이지만 실제로는 5개를 설정하도록 나온다. (동일 권한체크를 알아서 배제하더라)

 public class Login extends Activity {
    // 멀티 퍼미션 지정
    private String[] permissions = {
            Manifest.permission.READ_PHONE_STATE,
            Manifest.permission.CALL_PHONE, // 전화걸기 및 관리
            Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
            Manifest.permission.WRITE_EXTERNAL_STORAGE, // 기기, 사진, 미디어, 파일 엑세스 권한
            Manifest.permission.RECEIVE_SMS, // 문자 수신
            Manifest.permission.CAMERA
            };
    private static final int MULTIPLE_PERMISSIONS = 101;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        if (Build.VERSION.SDK_INT >= 23) { // 안드로이드 6.0 이상일 경우 퍼미션 체크
            checkPermissions();
        }

    }

    private boolean checkPermissions() {
        int result;
        List<String> permissionList = new ArrayList<>();
        for (String pm : permissions) {
            result = ContextCompat.checkSelfPermission(this, pm);
            if (result != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(pm);
            }
        }
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case MULTIPLE_PERMISSIONS: {
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++) {
                        if (permissions[i].equals(this.permissions[i])) {
                            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                                showToast_PermissionDeny();
                            }
                        }
                    }
                } else {
                    showToast_PermissionDeny();
                }
                return;
            }
        }

    }

    private void showToast_PermissionDeny() {
        Toast.makeText(this, "권한 요청에 동의 해주셔야 이용 가능합니다. 설정에서 권한 허용 하시기 바랍니다.", Toast.LENGTH_SHORT).show();
        finish();
    }

}


위 코드는 http://programmar.tistory.com/5 에 링크된 GitHub 소스에 포함되어 있던 건데 아주 약간 수정했다.


소스코드 첨부 (퍼미션 지정에 필요한 코드만 발췌 수정)

PermissionChk.java


개별로 사용하던 퍼미션 설정 코드도 첨부해둔다.

PermissionCheck_Indivisual.java



라이브러리를 사용하는 것이 훨씬 더 편하므로 아래 사이트를 참조하면 좋다.

https://brunch.co.kr/@babosamo/50


퍼미션 라이브러리 관련 사이트

https://www.androidhive.info/2017/12/android-easy-runtime-permissions-with-dexter/


https://thanosfisherman.github.io/posts/mayi/


http://gun0912.tistory.com/61  TED Permission (추천)


https://hanburn.tistory.com/173  RxPermission (추천)



블로그 이미지

Link2Me

,