728x90

Eclipse 에서 소스코드를 자동정렬 해주는 단축키는 Ctrl + Shift + F 다.

문제는 자동정렬의 width = 80 으로 고정시켜 코드를 자동정렬 시켜버린다.


width = 130 정도로 수정했더니 23인치 모니터 화면에 적정하게 보인다.

너비를 커스터마이징하는 방법이다.



이렇게 설정하고 나서, Ctrl + Shift + F 를 누르면 강제로 줄바꿈했던 코드를 한줄로 합쳐서 표시해준다.

블로그 이미지

Link2Me

,
728x90

Android Studio SQLite DB에 대해 다뤄보자.


안드로이드에서 데이터를 저장하는 방법은 다음과 같은 것들이 있다.

SharedPreference

 key - value 으로 저장하여 모든 Intent에서 활용 가능

 주로 서버 로그인후 idx, userID 등의 정보를 저장하여 활용한다.

 앱이 삭제되면 저장된 정보는 삭제된다.

 Internal Storage

 안드로이드는 리눅스 운영체제를 수정하여 만들었으며,

 /data/data/어플리케이션패키지명/files 디렉토리에 저장한다.

 앱이 삭제되면 어플리케이션패키지명이 삭제되므로 이 공간에 저장된 파일들도

 삭제된다.

 External Storage

 외부저장공간은 탈착이 가능한 SD카드다.

 안드로이드 장치는 외부 저장 공간을 지원하므로

 외부 저장공간에 파일을 저장할 수 있다.

 누구나 읽을 수 있고 사용자에의해 변경될 수 있다.

 SQLite 데이터베이스

 구조화된 데이터를 DB에 저장한다.

 SQLite는 초경량 데이터베이스로 데이터를 디스크 파일에 저장한다.

 데이터 정의 명령어는 테이블을 생성/변경/삭제한다.

 데이터 조작 명령어는 데이터를 조회/추가/삭제/수정한다.


Cursor 사용

Cursor 인터페이스는 데이터베이스 쿼리에 의해서 반환된 result 셋에 임시적으로 데이터를 읽고 쓰기 위해서 액세스할 수 있는 기능을 제공하는 인터페이스다.

인자

내용 설명

 moveToFirst

커서가 쿼리(질의) 결과 레코드들 중에서 가장 처음에 위치한 레코드를 가리키도록 한다.

 moveToLast

 마지막 행으로 이동

 moveToNext

 다음 레코드로 커서를 이동

 moveToPrevious

 이전 레코드로 커서를 이동

 moveToPosition(int position)

 파라미터로 주어진 절대 행 위치로 이동한다. 성공적이면 true 반환

 getCount

 질의 결과값(레코드)의 갯수를 반환, 조건이 없으면 총개수 반환

 getColumnCount

 총 칼럼 개수를 반환

 getColumnIndex

 해당 이름의 칼럼의 0 베이스 칼럼 인덱스 값을 반환해준다.

 해당 이름의 칼럼이 존재하지 않으면 -1을 반환한다.

 getColumnIndexOrThrow

 해당 이름을 가지고 있는 칼럼의 0 베이스 인덱스 번호를 반환한다.
 해당 이름의 칼럼이 존재하지 않으면 IllegalArgumentException 을 발생시킨다.

 getColumnName

 특정 인덱스값에 해당하는 필드 이름을 반환

 getColumnNames

 칼럼의 이름들을 String(문자열) 배열 형태로 반환

 moveToPosition

 커서를 특정 레코드로 이동시킨다

 getPosition

 커서가 현재 가리키고 있는 위치를 반환

 getDouble

 해당 인덱스의 칼럼 값을 double 형으로 반환한다.

 close()

 열려있는 커서를 닫는다.


SQLite 데이터베이스의 특성상 하나의 테이블의 레코드를 읽어 오기 위해서는 커서(cursor)라는 것이 필요하다.

조건에 맞는 레코드를 한꺼번에 모두 가져올 수 없기 때문에 커서(cursor)를 이용해서 조작을 한다.

커서(cursor)는 현재 레코드를 가리킨다. 하나씩 이 커서(cursor)를 이동하면서 레코드 하나하나씩을 접근해서 가져온다. 이 커서 객체를 이용하여 get을 하게 되면 컬럼 번호에 맞게 데이터를 가져올 수 있다.

커서는 정방향이나 역방향으로 움직일 수 있다.


SQLite 는 구글 안드로이드 운영체제에 기본 탑재된 데이터베이스로 비교적 가벼운 데이터베이스다.


SQL 기본 문법은 http://www.w3schools.com/sql/default.asp 에서 익히면 된다.

SQLite 는 완전히 독립적이고 서버 기능을 제공하지 않으며, 트랜잭션을 지원하며 쿼리를 수행할 수 있는 표준 SQL 언어를 사용한다.

SQLite 만의 문법사항을 알아야만 편하다. 간단하게 SQLite 의 문법 사항을 알아보자.

ㅇ Data Type : Null, Integer(정수), Real(실수), Text(문자열), Blob

    - Integer : 부호있는 정수, 실제 값에 따라 1byte ~ 8bytes까지 가변적으로 저장됨

    - Boolean values are stored as integers 0 (false) and 1 (true).

    - Date and Time 데이터 타입은 없다. datetime은 입력되는 값에 따라 Text, Real, Integer 타입으로 저장됨

ㅇ JOIN : Left Outer Join, INNER Join 지원

ㅇ Rename Table, Add Column 지원 (Drop Column, Alter Column, Add Constraint 미지원)

    - 한번에 여러 Column 추가 안된다.

    - Join Update 를 지원하지 않는다.

ㅇ Grant and Revoke : 별도로 권한 부여 기능이 없고, OS 파일 시스템 권한을 사용


SQLite 에 대한 자세한 설명은 http://overoid.tistory.com/19 에 잘 설명되어 있다.

영문 원본을 읽어보려면 http://www.sqlite.org/datatype3.html 를 보면 된다.


http://www.androidhive.info/2011/11/android-sqlite-database-tutorial/ 에 좋은 예제가 있다.



초기 데이터가 많은 경우 DB 파일을 assets 에 저장해두고 copy 해서 SQLite 저장하는 것은 해보지 않았다.

서버 데이터를 가져오는 케이스에 대한 로직을 그려본다면 ...

서버에 있는 자료를 읽어서 SQLite DB에 저장하고, SQLite DB에 저장된 데이터를 읽어서 ArrayList에 저장하고 ListView 또는 RecyclerView 를 통해 보여주거나, 폰에 주소록을 저장할 수도 있다.

안드로이드에서 데이터베이스를 사용하려면

- SQLiteOpenHelper 를 사용하는 방법

- openOnCreateDatabase() 메소드로 데이터베이스 객체를 직접 생성하는 방법

둘 중 하나를 선택해야 한다.


안드로이드에 내장되어 있는 SQLite을 사용하기 위해서는 SQLiteOpenHelper 클래스를 이용해야 한다.
SQLiteOpenHelper 클래스는 데이터베이스를 생성하고 버전을 관리 할 수 있도록 도와주는 클래스이다.

SQLiteOpenHelper 클래스를 이용하면 SQL 기본 사용법을 알면 쉽게 처리할 수가 있다.


SQLiteOpenHelper 를 사용하는 방법으로 코드를 구현해 보자.

 === SQLiteDBHandler ===

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class SQLiteDBHandler extends SQLiteOpenHelper {
    public static final String TAG = "DBHelper";

    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "mCRM.db";
    public static final String TABLE_NAME = "PBbook";

    public static final String Column_Uid = "uid";
    public static final String Column_Name = "name";
    public static final String Column_mobileNO ="mobileNO";
    public static final String Column_officeNO ="officeNO";
    public static final String Column_profile_image = "profile_image";

    public SQLiteDBHandler(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String Member_Create = "create table "+ TABLE_NAME + " (" +
                "idx INTEGER PRIMARY KEY AUTOINCREMENT,"+
                "uid INTEGER not null," +
                "name TEXT not null," +
                "mobileNO TEXT not null," +
                "officeNO TEXT," +
                "profile_image TEXT);";
        db.execSQL(Member_Create);
        Log.v(TAG,"DB Created");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.w("LOG_TAG", "Upgrading database from version " + oldVersion + " to " + newVersion
                + ", which will destroy all old data");
        // KILL PREVIOUS TABLES IF UPGRADED
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
        onCreate(db); //새로 생성하기
        Log.v(TAG,"DB Updated");
    }

    public void InsertData(String uid,String name, String mobileNO, String officeNO,String profile_image) {
        SQLiteDatabase db = getWritableDatabase(); // 쓰기 가능한 데이터베이스를 가져와 입력

        // 이름 + 휴대폰번호 기준으로 중복 체크
        String query = "select idx from " + TABLE_NAME
                + " where " + Column_Name + "= '"+ name +"' and " + Column_mobileNO + "= '" + mobileNO + "'";
        Cursor cursor = db.rawQuery(query, null);
        cursor.moveToFirst(); // Cursor를 제일 첫행으로 이동
        if( cursor.getCount() == 0) {  // 중복이 없으면 저장하라.
            ContentValues cv = new ContentValues(); // 객체 생성
            cv.put(Column_Uid, uid);
            cv.put(Column_Name, name);
            cv.put(Column_mobileNO, mobileNO);
            cv.put(Column_officeNO, officeNO);
            cv.put(Column_profile_image, profile_image);
            db.beginTransaction();  // 대량건수 데이터 입력 처리를 고려
            try{
                long rowId = db.insert(TABLE_NAME, null, cv);
                if(rowId < 0){
                    throw new SQLException("Fail to Insert");
                }
                db.setTransactionSuccessful();
            } catch(Exception e){
                Log.i(TAG,e.toString());
            } finally {
                db.endTransaction();
                Log.v(TAG,"DB Inserted " + name + " uid =" + uid);
            }
        }
        cursor.close();
        db.close();
    }

    /* Get the first row Column_ID from the table */
    public int getFirstId() {
        int idToUpdate = 0;
        String query = "select idx from " + TABLE_NAME + " LIMIT 1";

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor res = db.rawQuery(query, null);

        if (null != res && res.getCount() > 0) {
            res.moveToFirst(); // Cursor를 제일 첫행으로 이동
            idToUpdate = res.getInt(0);
        }
        return idToUpdate;
    }

    /* Update the table row with Column_ID - id */
    public boolean updateDB(Integer idx, String name, String mobileNO, String officeNO) {
        Log.i(TAG, "Updating Column_ID : " + idx);
        ContentValues cv = new ContentValues();
        cv.put(Column_Name, name);
        cv.put(Column_mobileNO, mobileNO);
        cv.put(Column_officeNO, officeNO);

        SQLiteDatabase db = this.getWritableDatabase();
        db.update(TABLE_NAME, cv, "idx = ? ", new String[]{Integer.toString(idx)});
        return true;
    }

    /* Delete the row with Column_ID - id from the employees table */
    public Integer deleteRow(Integer idx) {
        SQLiteDatabase db = this.getWritableDatabase();
        return db.delete(TABLE_NAME, "idx = ? ", new String[]{Integer.toString(idx)});
    }

    public int getTableRowCount() {
        String countQuery = "SELECT * FROM " + TABLE_NAME;
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(countQuery, null);
        Log.i(TAG, "Total Row : " + cursor.getCount());
        cursor.close();
        return cursor.getCount();
    }

    // Getting All Contacts
    public List<Address_Item> getAllPBook() {
        List<Address_Item> pbookData = new ArrayList<Address_Item>();
        // Select All Query
        String selectQuery = "SELECT  * FROM " + TABLE_NAME;

        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);

        // looping through all rows and adding to list
        if (cursor.moveToFirst() && cursor.getCount() > 0) {
            do {
                Address_Item contact = new Address_Item();
                contact.setUid(cursor.getString(1));
                contact.setName(cursor.getString(2));
                contact.setMobileNO(cursor.getString(3));
                contact.setOfficeNO(cursor.getString(4));
                contact.setProfile_image(cursor.getString(5));
                // Adding contact to list
                pbookData.add(contact); // DB에서 받아온 값을 ArrayList에 Add
            } while (cursor.moveToNext());
        }
        cursor.close();
        return pbookData; // return contact list
    }
}

 === Address_Item.java ===

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

    public Address_Item() {
    }

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

    public String getProfile_image() {
        return profile_image;
    }

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

    public String getUid() {
        return uid;
    }

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

    public String getName() {
        return name;
    }

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

    public String getMobileNO() {
        return mobileNO;
    }

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

    public String getOfficeNO() {
        return officeNO;
    }

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

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

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


MainActivity.java 파일에서 구현할 코드는 다음과 같다.


 안드로이드 운영 체제에 기본 탑재된 데이터베이스

출처: http://cocomo.tistory.com/409 [Cocomo Coding]
구글 안드로이드 운영 체제에 기본 탑재된 데이터베이스

출처: http://cocomo.tistory.com/409 [Cocomo Coding]

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

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

import java.util.List;

public class MainActivity extends AppCompatActivity {

    SQLiteDBHandler sqLiteDBHandler;
    public SharedPreferences settings;
    ProgressDialog mProgressDialog;

    TextView textView;
    Context context;

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

        textView = (TextView) findViewById(R.id.sqlite_txt);

        sqLiteDBHandler = new SQLiteDBHandler(MainActivity.this);
        // 서버에서 데이터를 가져와서 DB에 insert
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("idx", settings.getString("idx",""));
        String postParams = builder.build().getEncodedQuery();
        new getOrgChartData().execute(Value.IPADDRESS + "/get_json.php",postParams);

        // 저장버튼을 클릭하면 SQLite DB에 저장된 데이터를 폰의 주소록에 저장하는 기능
        Button btn_contactSave = (Button) findViewById(R.id.btn_sqlite_save);
        btn_contactSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<Address_Item> contacts = sqLiteDBHandler.getAllPBook();
                int num = 0;
                for(Address_Item item : contacts){
                    num++;
                    int rawContactId = 0;
                    Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                    ContentResolver cr = getContentResolver();
                    String strContactName = item.getName();
                    String strMobileNO = item.getMobileNO();
                    String strofficeNO = item.getOfficeNO();
                    String strEmail = "";
                    String strPhoto = "";
                    if (item.getProfile_image().length() > 0) {
                        strPhoto = Value.IPADDRESS + "/photos/" + item.getProfile_image();
                    }
                    rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName);
                    if (rawContactId > 0) {
                        // 기존 전화번호가 존재하면 삭제하고 새로 입력
                        phonebook.ContactsIDdelete(cr, context, rawContactId);
                    }
                    phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                }
                Toast.makeText(getApplicationContext(), "총" + num + "개 연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
            }
        });

    }

    class getOrgChartData 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;
            showJSONObjectList();
            mProgressDialog.dismiss();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    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 showJSONObjectList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                final String uid = c.getString(TAG_UID);
                final String name = c.getString(TAG_NAME);
                final String mobileNO = c.getString(TAG_MobileNO);
                final String officeNO = c.getString(TAG_OfficeNO);
                final String Profile_Image = c.getString(TAG_Image);

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

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

        textView.setText(String.valueOf(sqLiteDBHandler.getTableRowCount()));
    }

}


도움되셨다면 00 클릭~ 해주시길 ^^


참고하면 도움이 될 게시글

https://sites.google.com/site/ydhanslab/andeuloideu/oebusqlitedbneohgi


블로그 이미지

Link2Me

,
728x90

윈도우 운영체제에 리눅스를 설치하기 위해서 VMware 를 이용해서 CentOS 6.6 을 설치했다.


리눅스 설치가 되고 WinSCP, SSH 접속 모두 잘된다.


네트워크 환경설정하는 방법을 알아보고 적어둔다.




vi /etc/sysconfig/network-scripts/ifcfg-eth0 로 접속해서 위 정보를 토대로 수정한다.


vi /etc/resolv.conf 로 nameserver 를 추가한다.



/etc/init.d/network restart 로 네트워크를 재시작한다.


ping 168.126.63.1 로 테스트하면 성공적으로 패킷이 온다.


하지만 IPTime 공유기 외부에서 접속하는 건 성공하지 못했다.


http://blog.naver.com/bongss2/220830879059 참고해서 한번 해봐야겠다.



'리눅스' 카테고리의 다른 글

PHP mcrypt 동적 모듈 추가  (0) 2017.06.08
php sockets 동적 모듈 추가 (phpize)  (0) 2017.06.08
리눅스 시스템 종료  (0) 2017.05.19
sftp 파일 전송 shell script  (0) 2017.03.09
리눅스 파일/디렉토리 구조  (0) 2017.03.09
블로그 이미지

Link2Me

,
728x90

APM(Apache + PHP + MySQL) 소스설치 방식으로 PHP 버전은 5.6.30 버전을 설치했더니

Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead  메시지가 뜨면서 접속이 제대로 안된다.


기존 모든 소스를 수정해야 하나, APM 소스 설치를 다시 해야 하나 고민되어 검색을 했더니 이렇게 하라고 나온다.


$link = @mysql_connect($db_server,$db_user_name,$db_password);

@ 를 앞에 붙이라고 나온다.

오류 제어 연산자(@) : PHP의 코드가 오류가 있을 때 오류 메시지를 무시하도록 하는 연산자

오류 메시지를 어떤 상황에서든 출력하지 않도록 하고자 할 때 사용하는 연산자이다.


<?php
error_reporting(E_ALL ^ E_DEPRECATED);

이렇게 추가하라고 알려주기도 한다.


원인이 The mysql_* functions has been deprecated as of PHP 5.5.0 버전 이상부터 나오는 현상이란다.


근본적으로는 mysqli_* 방식이나 PDO 방식으로 변경해야 되는데 전부 수정하기에는 엄두가 나지 않는다.

일단 앞에 오류제어 연산자 @를 붙이는 방법으로 임시 조치하여 사용해보고 있다.


PHP 5.4.6 에서 정상 동작하는 코드가 PHP 5.6.30 에서 동작되지 않는 것이 있었는데 코드를 세부적으로 살펴보니 낮은 버전에서는 MySQL 연결 문법을 대략 생략해도 동작되던게 동작되지 않는 증상이라 수정처리했더니 잘 동작한다.


APM 소스 설치시 옵션에 따라서 약간씩 설치가 되는 사이트도 있고 아닌 곳도 있어서 설치하면서 고생을 좀 했다.

phpize 를 이용해서 미처 설치하지 못한 옵션을 추가하는 방법도 알게되고 난 후에는 여러모로 APM 소스 설치가 편리하기는 하더라.

PHP 5.6.30 에서 속도가 좀 더 개선(?)된 느낌이 들기는 한다.


다시 정리하자면

기존 코드 수정을 최소화하면서 이용하는 방법은

mysql_connect( $this->host, $this->user, $this->pass ) or die('error connection');

@mysql_connect( $this->host, $this->user, $this->pass ) or die('error connection');

로 해주면 없어진다.

mysql_connect( $this->host, $this->user, $this->pass ) or die('error connection');

@mysql_connect( $this->host, $this->user, $this->pass ) or die('error connection');

로 해주면 없어진다.



출처: http://link2me.tistory.com/1167 [소소한 일상 및 업무TIP 다루기]


제대로 변경하려면

연동에 사용되는 함수를 찾아서 전부 변경해줘야 한다.

$connect = mysqli_connect('localhost', 'user', 'password', 'dbname');


replace all mysql_* functions into mysqli_* functions


//PHP 5.4 o earlier (DEPRECATED)
$con = mysql_connect($host,$user,$password) or exit("Connection Error");
$conn = mysql_select_db($db, $con);

mysql_query("set names utf8");


//PHP 5.5 (New method)
$conn =  mysqli_connect($host,$user,$password,$db);



PDO (PHP Data Objects) 방식으로 연동

- 연동방식으로 만든 함수를 많이 수정해야만 정상동작되는거 같다. 기존 함수를 변환시 주의할 점이 좀 있다.

$conn = new PDO($hostDb, $user, $password);
또는

$conn = new PDO('mysql:host=localhost;dbname={$dbname};charset=utf8', $user, $password);

로 한다.


블로그 이미지

Link2Me

,
728x90

아이콘을 변환하는 사이트를 찾으려고 하니 찾는데 좀 걸리기도 해서 적어둔다.


ico 파일로 아주 쉽게 변환해준다.


http://convertico.com/


http://icoconvert.com/


https://iconverticons.com/online/


모바일 Web 아이콘을 만드는 방법이다.

<link rel="apple-touch-icon" href="./images/touch-icon-iphone.png">


아이폰은 57×57 , 아이패드는 72×72, 아이폰4는 114×114 사이즈의 png 이미지를 사용한다.

가능하면 114×114 이미지로 만들어두면 아이폰에서 자동으로 크기 리사이즈를 한다.






'iOS > Objective-C' 카테고리의 다른 글

objective-c UIView 기초  (0) 2018.12.17
objective-c 객체 Type  (0) 2018.12.15
objective-c 함수  (0) 2018.12.12
objective C 변수 타입  (0) 2018.12.09
NSLog  (0) 2018.03.01
블로그 이미지

Link2Me

,
728x90

안드로이드에서 Adapter 는 선택 위젯(ListView, GridView, Gallery, Spinner 등)에 일괄된 인터페이스를 제공해 준다.

특정 데이터를 읽어들인 후 특정 선택 기능에서 사용될 항목 View 를 만들어 선택 가능 위젯에 제공하는 역할을 한다.




ListView 는 Adapter 를 사용하여 데이터를 표시하는 View 이다.


1. DataSet (XML 파일 또는 서버 DB파일)에 있는 자료를 저장할 ArrayList 를 선언한다.

    ArrayList<String> items = new ArrayList<String>();


2. 파일을 가져와서 XmlPullParser 에 넣는다.

    END_DOCUMENT(종료 태그)가 나올때 까지 반복한다.

    반복하면서 TEXT 를 추출하여 1번의 items 에 자료를 저장한다.


XmlPullParser 는 문서를 순차적으로 읽으며 이벤트를 발생시킨다.

START_DOCUMENT 이벤트는 문서의 시작을
END_DOCUMENT 은 문서의 끝을
START_TAG는 태그의 시작을 (예 : <uid> )
END_TAG는 태그의 끝을 (예 : </uid> )
TEXT는 태그의 시작과 끝 사이에서 나타난다. (예 : <uid>여기서 TEXT 이벤트 발생</uid> )

XmlPullParser는 순차적으로(한줄한줄) 문서를 읽어가면서 이벤트를 발생시키므로 뒤로 돌아가지 못하는 문제가 있다.

그래서 이를 해결하기 위해
XmlPullParser.START_TAG 이벤트가 발생하면 임시변수(String)에 Tag값을 저장하고
XmlPullParser.TEXT이벤트에서 임시변수에 저장된 Tag값을 확인하여 적절한 변수에 값을 넣어야한다.


주의 : XmlPullParser.START_TAG와 XmlPullParser.END_TAG 에서는 getName()을 사용하여야 하고 XmlPullParser.TEXT에서는 getText()를 사용하여야한다. 그렇지 않으면  null값을 반환한다.
XmlPullParser.TEXT 이벤트는 태그의 택스트 영역에 문자가 존재하지 않아도 발생한다.




블로그 이미지

Link2Me

,
728x90

이제 ListView 에 간단하게 출력하는 예제를 살펴보자.


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

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#e7e7e7"
        />
</LinearLayout>


Student.java 클래스를 만든다.




이렇게 하면 파일이 완성된다.

public class Student {
    String name;
    String age;
    String address;

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}


이제 MainActivity.java 파일 코드를 구현한다.

아래 MainActivity.java 에서 case XmlPullParser.START_TAG: 부분을 살펴보자.

startTag 의 이름이 name 이면 다음 텍스트를 변수에 저장하라고 되어 있는 걸 알 수 있다.

이처럼 START_TAG 부분에서 텍스트를 어떻게 추출하는지 알 수 있다.

LOG 는 일부러 세부적으로 찍어보도록 했다. 로그를 확인하면서 어떻게 메시지를 파싱하는지 확인할 수 있다.


String startTag = xmlParser.getName();
if(startTag.equals("student")){
    student = new Student();
}
if(startTag.equals("name")){
    student.setName(xmlParser.nextText());
    Log.i(TAG,"TEXT : "+ xmlParser.getText());
    Log.i(TAG,"TEXT : "+ xmlParser.getName());
    Log.i(TAG,"TEXT : "+ student.getName());
}


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    final static String TAG ="XML";
    ListView listView;

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

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

        ArrayList<Student> list = parser();
        String[] data = new String[list.size()]; // ArrayList 크기만큼 배열 할당

        for(int i = 0; i < list.size(); i++){
            data[i] = list.get(i).getName()+" "+list.get(i).getAge()
                    +" "+list.get(i).getAddress();
        }

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),
            android.R.layout.simple_list_item_1, data);
        listView.setAdapter(adapter);
    }

    private ArrayList<Student> parser(){
        Log.i(TAG, "parser()");
        ArrayList<Student> arrayList = new ArrayList<Student>();

        // 내부 xml파일이용시
        InputStream inputStream = getResources().openRawResource(R.raw.student);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

        XmlPullParserFactory factory = null;
        XmlPullParser xmlParser = null;

        try {
            factory = XmlPullParserFactory.newInstance();
            xmlParser = factory.newPullParser();
            xmlParser.setInput(inputStreamReader);
            Student student = null;

            int eventType = xmlParser.getEventType();

            while (eventType != XmlPullParser.END_DOCUMENT){
                switch (eventType){
                    case XmlPullParser.START_DOCUMENT:
                        Log.i(TAG, "xml START");
                        break;
                    case XmlPullParser.START_TAG:
                        Log.i(TAG, "Start TAG :" + xmlParser.getName());
                        try {
                            String startTag = xmlParser.getName();
                            if(startTag.equals("student")){
                                student = new Student();
                            }
                            if(startTag.equals("name")){
                                student.setName(xmlParser.nextText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ student.getName());
                            }
                            if(startTag.equals("age")){
                                student.setAge(xmlParser.nextText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ student.getAge());
                            }
                            if(startTag.equals("address")){
                                student.setAddress(xmlParser.nextText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ student.getAddress());
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        Log.i(TAG,"End TAG : "+ xmlParser.getName());
                        String endTag = xmlParser.getName();
                        if(endTag.equals("student")){
                            arrayList.add(student);
                        }
                        break;
                }
                try {
                    eventType = xmlParser.next();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } finally{
            try{
                if(inputStreamReader !=null) inputStreamReader.close();
                if(inputStream !=null) inputStream.close();
            }catch(Exception e2){
                e2.printStackTrace();
            }
        }
        return arrayList;
    }
}


결과화면


샘플 코드 파일 첨부

xml_internaldataParsing.zip


블로그 이미지

Link2Me

,
728x90

어플 내부에 있는 XML 파일을 파싱하는 예제다.

=== student.xml ===

 <person>
    <student>
        <name>홍길동</name>
        <age>32</age>
        <address>용인</address>
    </student>
    <student>
        <name>이순신</name>
        <age>31</age>
        <address>서울</address>
    </student>
    <student>
        <name>강감찬</name>
        <age>35</age>
        <address>충청</address>
    </student>
</person>


예제는 화면 출력 용도가 아니라 로그파일에 기록되는 결과를 보기 위한 목적이다.

파싱하는 소스 코드

- 먼저 layout 디렉토리 밑에 raw 라는 디렉토리를 생성한다.

- raw 디렉토리에 student.xml 파일을 생성하고 xml 내용을 추가한다.

- MainActivity.java 에서 아래와 같은 코드를 작성한다.


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class MainActivity extends AppCompatActivity {
    final static String TAG ="XML";

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

        parser();
    }

    private void parser(){
        Log.i(TAG, "parser()");

        // 내부 xml파일이용시
        InputStream inputStream = getResources().openRawResource(R.raw.student);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader reader = new BufferedReader(inputStreamReader);

        XmlPullParserFactory factory = null;
        XmlPullParser xmlParser = null;

        try {
            factory = XmlPullParserFactory.newInstance();
            xmlParser = factory.newPullParser();
            xmlParser.setInput(reader);

            int eventType = xmlParser.getEventType();

            while (eventType != XmlPullParser.END_DOCUMENT){
                switch (eventType){
                    case XmlPullParser.START_DOCUMENT:
                        Log.i(TAG, "xml START");
                        break;
                    case XmlPullParser.START_TAG:
                        Log.i(TAG, "Start TAG :" + xmlParser.getName());
                        break;
                    case XmlPullParser.END_TAG:
                        Log.i(TAG,"End TAG : "+ xmlParser.getName());
                        break;
                    case XmlPullParser.TEXT:
                        Log.i(TAG,"TEXT : "+ xmlParser.getText());
                        break;
                }
                try {
                    eventType = xmlParser.next();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } finally{
            try{
                if(reader !=null) reader.close();
                if(inputStreamReader !=null) inputStreamReader.close();
                if(inputStream !=null) inputStream.close();
            }catch(Exception e2){
                e2.printStackTrace();
            }
        }


    }
}


이제 컴파일을 하고 결과를 살펴본다.






이제 다음에는 ListView 에 출력하는 걸 해보면 XML 파싱을 정확하게 파악할 수 있다.

블로그 이미지

Link2Me

,
728x90
# shutdown -H now

will halt the system - meaning the system will shutdown and at the end stop at a screen with the last message beeing something like "System halted".

# shutdown -P now

will power off the system - meaning the system will shutdown and at the end power off (only possible if the system actually supports it but most systems I know have for quite a while now)

# shutdown -h now

will only halt or power off the system depending on what's the default on that system (can sometimes be changed in BIOS)

블로그 이미지

Link2Me

,
728x90

네이버 지식인에 문의사항이 있길래 테이블 생성하는 방법을 적는다.


테이블 설계는 보통 phpMyAdmin 상에서 하면 편리하므로 굳이 PHP 코드 상에서 할 필요성을 느끼지 못해서 적어두지 않았다.


테이블 생성은 간단하다.


==== dbinfo.php ===

<?php
$db['host'] = "localhost";
$db['name'] = "csharp";
$db['user'] = "root";  // 원래는 root 사용하면 안되는데 연습용인지라...
$db['pass'] = "autoset";
$db['port'] = "3306";
?>


==== dbconnect.php ===

<?php
include_once 'dbinfo.php';
$connect = isConnectDb($db);

function isConnectDb($db)
{
    $conn = mysqli_connect($db['host'],$db['user'],$db['pass'],$db['name'],$db['port']);
    mysqli_set_charset($conn, "utf8");  // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
    if (mysqli_connect_errno()) {
        echo "Failed to connect to MySQL: " . mysqli_connect_error();
        exit;
    } else {
        return $conn;
    }
}
?>


==== tablecreate.php ====

<?php
include_once 'dbconnect.php'; // DB 연결

$sql ="
CREATE TABLE IF NOT EXISTS member (
no int(11) not null auto_increment,
id varchar(15) NOT NULL,
user_id varchar(15) NOT NULL,
name varchar(15) NOT NULL,
nick_name varchar(15),
birth varchar(8),
sex varchar(6),
tel varchar(8),
email varchar(40),
pw varchar(32) NOT NULL,
addr_1 varchar(100),
addr_2 varchar(100),
level int,
regdate char(20),
ip varchar(20),
PRIMARY KEY(no)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
";

$result=mysqli_query($dbconn,$sql);
if($result) {
    echo "DB table created !!!";
} else {
    echo "Error creating table : " . mysqli_error($dbconn);
}

mysqli_close($dbconn);
?>


여기서 한가지 알아야 할 사항은 테이블 생성시 CREATE TABLE IF NOT EXISTS member 를 하면 결과는 OK 로만 나온다. 이미 테이블이 생성되어 있으면 에러 메시지를 보이도록 하려면 CREATE TABLE member 로 수정해주면 된다.


테스트에 사용된 코드 첨부


tablecreate.zip




블로그 이미지

Link2Me

,
728x90

어플 기능 개선이나 버그 수정 등으로 업그레이드를 해야 할 경우가 생긴다.

이때 업그레이드 여부 검사를 하는 로직과 파일 다운로드 기능을 구현했다.


구글링으로 검색하면 파일 다운로드 로직은 많이 나온다.

http://www.androidbegin.com/tutorial/android-download-progress-bar/

약간씩 설명이 다르지만 대부분 유사한 설명이 나온다.


서버의 버전과 어플의 버전을 비교하여 서버의 버전이 높으면 파일을 자동으로 다운로드하는 로직으로 구현했다.

기본적인 개념은 http://link2me.tistory.com/1277 파일을 실행해보면 알 수 있다.

파일을 다운로드하면 상태바가 활성화되고, 다운로드 하지 않을 경우에는 화면에 보이지 않도록 UI 를 선택적으로 처리했다.


 import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class MainActivity extends AppCompatActivity {
    public SharedPreferences settings;
    static final int PERMISSION_REQUEST_CODE = 1;
    String[] PERMISSIONS = {"android.permission.READ_EXTERNAL_STORAGE","android.permission.WRITE_EXTERNAL_STORAGE"};

    LinearLayout linearLayout;
    TextView textView;
    ProgressBar progressBar;
    DownloadFileFromURL downloadFileAsyncTask;
    static boolean isUpgrade = false;

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

        // 네트워크 연결 검사
        if(NetworkConnection() == false){
            NotConnected_showAlert();
        }

        if (!hasPermissions(PERMISSIONS)) { //퍼미션 허가를 했었는지 여부를 확인
            requestNecessaryPermissions(PERMISSIONS);//퍼미션 허가안되어 있다면 사용자에게 요청
        }

        // Linear layout 정의
        linearLayout = (LinearLayout) findViewById(R.id.downloadprogress_layout);
        textView = (TextView)findViewById(R.id.txtView01);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);

        // 업그레이드 유무 검사
        VersionCheck();

        if(isUpgrade == false){
            linearLayout.setVisibility(View.GONE);
        } else {
            linearLayout.setVisibility(View.VISIBLE);
        }

        //취소 버튼 이벤트 처리 및 리스너 등록
        Button btn_Cancel = (Button)findViewById(R.id.btnCancel);
        btn_Cancel.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                downloadFileAsyncTask.cancel(true);
            }
        });

    }

    private void VersionCheck(){
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("idx", "2"); // settings.getString("idx","" )
        String postParams = builder.build().getEncodedQuery();

        new isUpgradeTask().execute(Value.IPADDRESS + "/file/UpgradeChk.php", postParams);
    }

    private void UpgradeChk(final String serverVersion){

        try {
            String Appversion = Value.VERSION;
            Appversion = Appversion.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
            System.out.println("App Version : " + Appversion + " Server Version : " + serverVersion);

            if (Integer.parseInt(Appversion) < Integer.parseInt(serverVersion)) { // 서버 버전이 더 높으면
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setTitle("버전 업그레이드");
                builder.setMessage("버전 업그레이드 되었습니다. 새로운 버전으로 다시 설치해주십시오.");
                builder.setPositiveButton("업데이트", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(getApplicationContext(), "파일 다운로드 중입니다.", Toast.LENGTH_SHORT).show();
                        isUpgrade = true;
                        linearLayout.setVisibility(View.VISIBLE);
                        // 백그라운드 객체를 만들어 줌
                        downloadFileAsyncTask = new DownloadFileFromURL(MainActivity.this);
                        downloadFileAsyncTask.execute(Value.IPADDRESS + "/file/apkDownload.php");
                    }
                });
                builder.setNegativeButton("취소", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 업데이트 취소시 어플을 완전 종료시킴
                        moveTaskToBack(true);
                        finish();
                        android.os.Process.killProcess(android.os.Process.myPid());
                    }
                });
                builder.create();
                builder.show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    class isUpgradeTask extends AsyncTask<String,String,String>{
        @Override
        protected String doInBackground(String... params) {
            BufferedReader bufferedReader = null;
            String Response = null;
            try {
                URL url = new URL(params[0]);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

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

                    // Send post request
                    DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                    wr.writeBytes(params[1]);
                    wr.flush();
                    wr.close();

                    int responseCode = conn.getResponseCode();
                    System.out.println("GET Response Code : " + responseCode);
                    if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                        bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                        Response = bufferedReader.readLine();
                        Response = Response.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
                    }
                    bufferedReader.close();
                }
                return Response;
                // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
            } catch(Exception e){
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result) {
            UpgradeChk(result);
        }

    }

    class DownloadFileFromURL extends AsyncTask<String,Integer,String>{
        private Context context;
        private String savePath;
        private File file, dir;
        int count;

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

        protected void onPreExecute() {
            super.onPreExecute();
            progressBar.setProgress(0);
        }

        protected String doInBackground(String... params) {
            URLConnection connection = null;
            InputStream input = null;
            OutputStream output = null;
            int lenghtOfFile =0;

            savePath = "/Android/data/" + getPackageName();
            dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), savePath);
            if (!dir.exists()) dir.mkdirs(); // make dir
            file = new File(dir, Value.APKNAME);  // APK 이름 변경

            try {
                URL url = new URL(params[0]);
                try {
                    connection = url.openConnection();
                    connection.connect();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                lenghtOfFile  = connection.getContentLength(); //파일 크기를 가져옴
                if (file.exists()) {
                    file.delete(); // 파일이 존재하면 기존 파일을 삭제
                }

                try {
                    input = new BufferedInputStream(url.openStream());
                    output = new FileOutputStream(file);
                    byte data[] = new byte[1024];
                    long total = 0;

                    while ((count = input.read(data)) != -1) {
                        if (isCancelled()) {
                            input.close();
                            return String.valueOf(-1);
                        }
                        total = total + count;
                        if (lenghtOfFile > 0) { // 파일 총 크기가 0 보다 크면
                            publishProgress((int)(total * 100 / lenghtOfFile));
                        }
                        System.out.println("Download Percent === " + (int)(total * 100 / lenghtOfFile));
                        System.out.println("Total === " + total);
                        output.write(data, 0, count); //파일에 데이터를 기록
                    }
                    System.out.println("Download total Size === " + lenghtOfFile);

                    output.flush();
                    output.close();
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            //백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
            progressBar.setProgress(progress[0]);
            textView.setText("진행정도 : "+progress[0]+"%");
        }

        protected void onPostExecute(String result) {
            //백그라운 작업이 끝난후에 호출되는 메소드
            System.out.println("result === " + result);
            linearLayout.setVisibility(View.GONE);
            if ( result == null ) {
                Toast.makeText(getApplicationContext(), "다운로드 완료되었습니다.", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                context.startActivity(intent);
            } else {
                Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show();
            }
        }

        protected void onCancelled(){
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            // 작업을 취소했을 경우에 사용되는 메소드
            progressBar.setProgress(0);
            textView.setText("다운로드 진행 취소됨");
        }
    }

    private boolean hasPermissions(String[] permissions) {
        int res = 0;
        //스트링 배열에 있는 퍼미션들의 허가 상태 여부 확인
        for (String perms : permissions){
            res = checkCallingOrSelfPermission(perms);
            if (!(res == PackageManager.PERMISSION_GRANTED)){
                //퍼미션 허가 안된 경우
                return false;
            }
        }
        return true;  //퍼미션이 허가된 경우
    }

    private void requestNecessaryPermissions(String[] permissions) {
        //마시멜로( API 23 )이상에서 런타임 퍼미션(Runtime Permission) 요청
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(permissions, PERMISSION_REQUEST_CODE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){
        switch(permsRequestCode){
            case PERMISSION_REQUEST_CODE:
                if (grantResults.length > 0) {
                    boolean readAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    boolean writeAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if( !readAccepted || !writeAccepted  ) {
                            showDialogforPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다.");
                            return;
                        }
                    }
                }
                break;
        }
    }

    private void showDialogforPermission(String msg) {

        final AlertDialog.Builder myDialog = new AlertDialog.Builder(  MainActivity.this);
        myDialog.setTitle("알림");
        myDialog.setMessage(msg);
        myDialog.setCancelable(false);
        myDialog.setPositiveButton("예", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    requestPermissions(PERMISSIONS, PERMISSION_REQUEST_CODE);
                }

            }
        });
        myDialog.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
                finish();
            }
        });
        myDialog.show();
    }

    public void NotConnected_showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid() );
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public boolean NetworkConnection() {
        int[] networkTypes = {ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI};
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if(activeNetwork != null && activeNetwork.getType() == networkType){
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }
}

 import android.support.v7.app.AppCompatActivity;

public class Value extends AppCompatActivity {

    public static final String IPADDRESS = "http://192.168.0.2"; // SERVER IP
    public static final String VERSION = "1.0.0"; // App Version
    public static final String APKNAME = "AddressBook.apk" ; //APK name

}


<?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"
    tools:context="com.tistory.link2me.filedownload.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="10dp"
        android:text="업그레이드 체크"  />

    <LinearLayout
        android:id="@+id/downloadprogress_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/txtView01"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="18dp"
            android:padding="10dp"
            android:text="Progress Status"/>

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

            <ProgressBar
                android:id="@+id/progressBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
                android:layout_marginTop="5dp"
                android:max="100"
                android:padding="10dp"
                android:layout_weight="8" />
            <Button
                android:id="@+id/btnCancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginTop="5dp"
                android:layout_marginLeft="10dp"
                android:text="취소"
                android:textSize="12dp"
                />
        </LinearLayout>

    </LinearLayout>

</LinearLayout>


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

    <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.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

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

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

</manifest>


블로그 이미지

Link2Me

,
728x90

파일 다운로드 함수를 구현하기에 앞서 AsyncTask 다운로드 상태바 표시되는 예제다.

동영상 강좌 듣는 것을 테스트 하면서 명칭 일부를 수정한 거다.

AsyncTask 개념 설명은 http://link2me.tistory.com/1031 을 참조하면 된다.

실제 코드 구현하면서 에러 발생하는 부분 등을 같이 포함해서 기록해 두고 있다.


AsyncTask_ex01.zip


import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    ProgressBar pBar;
    DownloadFileAsyncTask downloadFileAsyncTask;

    int value;

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

        textView = (TextView)findViewById(R.id.txtView01);
        pBar = (ProgressBar)findViewById(R.id.progressBar);

        Button btn_Start = (Button)findViewById(R.id.btnStart);
        btn_Start.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                // 백그라운드 객체를 만들어줌
                downloadFileAsyncTask = new DownloadFileAsyncTask();
                downloadFileAsyncTask.execute();
            }
        });

        Button btn_Cancel = (Button)findViewById(R.id.btnCancel);
        btn_Cancel.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                downloadFileAsyncTask.cancel(true);
            }
        });
    }

    class DownloadFileAsyncTask extends AsyncTask<Integer, Integer, Integer> {

        protected void onPreExecute() { //초기화 단계에서 사용되는 메소드
            value = 0;
            pBar.setProgress(value);
        }

        protected Integer doInBackground(Integer... params) {
            // doInBackground 메소드는 필수 메소드
            while(isCancelled() == false){
                value++;
                if(value >=100){
                    break;
                }else{
                    publishProgress(value);
                }
                try{
                    Thread.sleep(100);
                }catch(Exception e){}
            } //while of End

            return value;
        }

        protected void onProgressUpdate(Integer... values) {
            //백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
            pBar.setProgress(values[0].intValue());
            textView.setText("작업 진행정도 : "+values[0].toString()+"%");
        }

        protected void onPostExecute(Integer result) {
            //백그라운 작업이 끝난후에 호출되는 메소드
            pBar.setProgress(0);
            textView.setText("작업 진행 완료");
        }

        protected void onCancelled(){
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            // 작업을 취소했을 경우에 사용되는 메소드
            pBar.setProgress(0);
            textView.setText("작업 진행 취소됨");
        }
    }
}

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

    <TextView
        android:id="@+id/titleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="AsyncTask Exercise"
        android:textSize="20dp"
        android:gravity="center"
        android:textColor="@color/colorAccent"
        android:layout_margin="10dp"
        />

    <TextView
        android:id="@+id/txtView01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18dp"
        android:padding="10dp"
        android:text="Progress Status"/>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_marginTop="5dp"
        android:max="100"
        android:padding="10dp"
        />

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

        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:layout_marginRight="20dp"
            android:text="시작"
            android:textSize="18dp"/>
        <Button
            android:id="@+id/btnCancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:text="취소"
            android:textSize="18dp"
            />

    </LinearLayout>
</LinearLayout>



블로그 이미지

Link2Me

,
728x90

permission denied for this window type/BadTokenException


윈도우 팝업창이 뜨지 않고 에러가 발생하면서 죽는다.

검색해보네 API Level 19 이상에서는

WindowManager.LayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT

을 사용하지 말고

LayoutParams.TYPE_TOAST or TYPE_APPLICATION_PANEL

를 사용하라고 나온다.


그래서 아래와 같이 변경했더니 앱이 죽지 않고 팝업창이 잘 뜬다.


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);

블로그 이미지

Link2Me

,
728x90

오늘 Android Studio 기반 어플을 테스트 하는데 PHP Array 를 잘못 구현해서 삽질을 한참 했다.


 // 사무실번호, 휴대폰번호, 이름, 소속 등의 정보를 추출
$sql = "select userNM,mobileNO,telNO,sosok from Person ";
$sql .= "where mobileNO='".$search."'";

$R = array(); // 결과 담을 변수 생성
$result = mysqli_query($dbconn,$sql);
while($row = mysqli_fetch_object($result)) {
    array_push($R, $row);
}
echo json_encode(array("result"=>$R));


모든 정보를 select 문으로 가져와서 array_push 에 담은 경우에는 아무런 문제가 되지 않는다.


그런데 sosok 정보를 서브 쿼리로도 가져오기가 힘든 경우가 생겼다.

그래서 함수를 만들어서 해당 정보를 가져와서 array_push 에 저장할 때 아무 생각없이 코딩을 했더니 원하는 결과가 나오지 않고 엉뚱한 결과가 나온다.


$search = add_hyphen_telNo($search);
$sosok = Phone2Dept($search);

// 사무실번호, 휴대폰번호, 이름, 소속 등의 정보를 추출
$sql = "select userNM,mobileNO,telNO from SYS_MEMBER ";
$sql.= "where mobileNO='".$search."' or telNO='".$search."' ";

$R = array(); // 결과 담을 변수 생성
$result = mysqli_query($dbconn,$sql);
while($row = mysqli_fetch_object($result)) {
    array_push($R, $row);

    array_push($R, array("sosok"=>$sosok));

}

echo json_encode(array("result"=>$R));
 


잘못된 결과가 나온다.


아래와 같이 풀어서 해결했다. 풀어서 array_push 에 담으면 암호화된 코드를 추가하기도 편하다.


$sosok = Phone2Dept($search);

// 사무실번호, 휴대폰번호, 이름, 소속 등의 정보를 추출
$sql = "select userNM,mobileNO,telNO from SYS_MEMBER ";
$sql.= "where mobileNO='".$search."' or telNO='".$search."' ";

$R = array(); // 결과 담을 변수 생성
$result = mysqli_query($dbconn,$sql);
while($row = mysqli_fetch_array($result)) {
    array_push($R, array("userNM"=>$row[0],"mobileNO"=>$row[1],"telNO"=>$row[2],"sosok"=>$sosok));
}
echo json_encode(array("result"=>$R));


select 문 하나로 해결이 안되는 걸 json 방식으로 안드로이드폰과 통신하려면 위와 같은 방법으로 데이터를 추가하면 해결될 수 있다.


참고 : How to Use JSON Data with PHP or JavaScript

https://www.taniarascia.com/how-to-use-json-data-with-php-or-javascript/


Android 코드에서는

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


로 로그 결과를 확인해서 보낸 데이터가 원하는 형태로 넘어오는지 확인을 꼭 하는게 좋다.

              

블로그 이미지

Link2Me

,
728x90

Eclipse 에서 workspace 개념에 해당되는 부분이 Android Studio 에서는 Project 라고 보면 된다.


그림에서 보는 바와 같이 app, connhandler, looper, thread_progressbar 는 모듈이다.

Eclipse 의 프로젝트 라고 볼 수 있다.



한 프로젝트에서 모듈만 새로 추가하면서 작성하면 위 그림처럼 나온다.

그럼 모듈을 새로 추가를 해보자.







모듈이 추가된 것을 확인할 수 있다.


비슷한 유형이라면 모듈을 추가해서 관리하면 기존 소스 복사도 편하고 좋다.




블로그 이미지

Link2Me

,
728x90

상단에 액션바가 차지하고 있어서 눈에 거슬린다.

상단 액션바를 안보이게 처리하는 방법을 알아봤는데 어떨 때는 되고 안될때도 있는데 API 버전에 따라 다른거 같기도 하다.


AndroidManifest.xml 파일에서 수정할 부분

android:theme="@style/Theme.AppCompat.NoActionBar"


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

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

 <application
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:supportsRtl="true"
  android:theme="@style/Theme.AppCompat.NoActionBar">
  <activity android:name=".Intro">
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />

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


이것만 수정하고 컴파일을 하면 화면이 검은 바탕색으로 변경되어 버린다.


activity_main.xml 파일 수정

배경색 추가를 한다. 배경색을 하얀색으로 변경하면 보이던 글자가 안보이는 현상이 생겼다.

그래서 색상을 맞춘다고 @color/whitegray  = #f0f0f0 로 만들었다.

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

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


문제는 또 발생한다. 모든 xml 파일 상단에 배경색을 저장해줘야 하는 문제점이 보인다.

검정색 글씨로 보여야 되는데 하얀색 글자로 보이면서 가독성이 엄청 떨어져 보인다.

배경색을 lightgray 로 변경했더니 하얀색 글자가 보이기는 하는데 영 불편하다.


결국 해결한 방법은


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

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


부분은 그대로 두고 참조하는 style.xml 파일 내용을 수정한다.

<item name="windowNoTitle">true</item> 라인을 추가한다.


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="windowNoTitle">true</item>
    </style>

</resources>



해결방법 2.

=== strings.xml ===

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Light" />
    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

=== AndroidManifest.xml ===

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

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






블로그 이미지

Link2Me

,
728x90

안드로이드 Checkbox

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

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


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

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


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

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


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

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


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

- 전체 선택 및 해제 : 잘 됨

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


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

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

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



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

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

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

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

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


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

    public Address_Item() {
    }

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

    public String getProfile_image() {
        return profile_image;
    }

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

    public String getUid() {
        return uid;
    }

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

    public String getName() {
        return name;
    }

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

    public String getMobileNO() {
        return mobileNO;
    }

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

    public String getOfficeNO() {
        return officeNO;
    }

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

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

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


=== MainActivity.java ====


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

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

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

public class MainActivity extends AppCompatActivity {

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

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

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

    RelativeLayout relative1;
    RelativeLayout relative2;

    Context context;

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

        isMSG = false;
        isAll = false;

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

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

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

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

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

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

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

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

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

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

    }

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

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

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

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

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

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

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

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

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

    }

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

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

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

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

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

            arrayList.add(item);
        }
    }


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

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

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

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

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

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

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

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

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

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

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

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

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

                }
            }

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

            return convertView;
        }

}

블로그 이미지

Link2Me

,
728x90

객체 지향 프로그래밍(Object-Oriented Programming: OPP)은 데이터와 절차를 하나의 덩어리(객체)로 묶어서 생각하는 방법이다.
객체 안의 변수를 필드(field)라 부르고, 객체 안의 함수를 메소드(method)라 부른다.
하나의 객체(Object)는 필드와 메소들 이루어진 소프트웨어의 묶음이다.

자바는 객체 지향 기법을 지원하지만 객체 지향을 사용한다고 해서 절차 지향 프로그램을 하지 않는다는 것이 아니다. 실제로 객체의 내부를 설계할 때는 절차 지향 프로그래밍을 사용한다.


절차지향 언어의 특징

- 절차 지향 프로그래밍이란, 프로그래밍을 순서, 즉 절차에 따라 프로그래밍하는 것을 의미한다.

- 절차 지향의 대표적인 프로그램은 C언어이다.


객체지향 언어의 특징

- 사람(주체)가 바라본 대상(사람, 사물, 객체 등)을 객체(object)라고 한다.

- 객체지향 언어는 사람이 일상적으로 보고, 느끼고, 생각하는 관점에서 프로그래밍을 하는 것이다.

- 객체 지향 프로그램의 첫번째 단계는 객체의 추출이다.

  객체는 각각 속성과 기능들을 가진다.


Class (클래스)

- 클래스는 설계서(붕어빵틀)다. 즉, 실체가 아니라 타입이다.

- Java의 모든 클래스는 Object 클래스를 상속한다.

- new 명령으로 해당 클래스 타입의 객체를 생성한 후, 그 객체에 데이터를 저장하고, 그 객체의 멤버 메소드를 실행하는 것이다.

  Contacts phonebook; // 클래스 타입 변수 선언

  phonebook = new Contacts(); // 전화번호부 실제 객체 생성 (변수 = new 클래스명();)

  인스턴스 변수는 반드시 객체를 생성해야만 호출이 가능하다.

  참조 변수 지정과 객체 생성을 한번에 할 수 있다.

  Contacts phonebook = new Contacts(); // 전화번호부 객체 생성, Class 타입 변수명 = new 생성자 ();

  phonebook .number = "010-0000-0000"; // Class 인스턴스를 생성한 다음 참조변수 phonebook 을 통해야 한다.

  참조변수의 특징은 메모리에 생성되어 있는 인스턴스의 위치값을 지정한다.

- final 메소드는 Class 상속되는 것은 허용하되, 메소드의 오버라이딩은 허용하지 않겠다는 의미다.


메서드(함수)가 호출되면 수행에 필요한 만큼의 메모리 스택에 할당을 받는다.

메서드(함수)가 수행을 마치고 나면 사용했던 메모리를 반환하고 스택에서 제거된다.


같은 Class 에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.

static 메서드(함수)는 인스턴스 메서드를 호출할 수 없다.

인스턴스 메서드(함수)는 인스턴스 메서드와 클래스 메서드 모두 인스턴스 생성없이 바로 호출이 가능하다.

클래스 변수가 인스턴스 변수를 참조, 호출하기 위해서는 반드시 객체를 생성해야 한다.


참조형 변수는 메모리에 생성되어 있는 인스턴스의 위치값을 저장한다.


 Premitive Type 변수

 - byte, short, int, long, float, double, char, boolean 8가지 타입을 제공한다.

 - type 마다 size가 fix 되어 있다.
 - 기본값이 있으므로 null이 존재하지 않는다.
 - generic을 이용할 때 필요한 wraper class가 존재한다. (Integer, Long..)
 - 컴파일 시, 크기를 초과하면 에러가 발생한다.

 Reference Type 변수

 - 기본형을 제외한 모든 타입은 Reference Type 이며 java.lang.Object 를 상속 받는다.

 - 참조형에는 Class Type, Interface Type, Array Type이 있다.

 - 기본값은 아무런 참조 정보가 없으므로 null을 리턴한다.

 - 생성된 객체는 Heap Memory 에 저장된다.

 primitive type의 == 연산은 값을 비교한다.
 reference type의 == 연산은 주소를 비교한다.
 리터럴 방식을 통해 만든 변수와, new를 통해 저장되는 메모리가 다르다.
 두 변수의 참조 메모리가 서로 다르다는 뜻이다.
 reference 타입에서 설명한 대로 new로 만든 객체는 Heap 영역에 저장이 되고,
 리터럴 방식으로 만들어진 객체는 String Constant Pool영역에 저장되기 때문이다.

 primitive Type은 value를 전달한다.
 reference Type은 reference를 전달한다.


 String은 reference Type 으로 다른 reference Type과 다른점은 JVM에서 String constant pool을 이용해 메모리 관리를 한다.

 - if(str == input) {} // str 이 String 변수이면 == 로 비교할 수 없다.

 - if(str.equals(input)) 으로 비교해야 한다.

 변수는 사용하기 전에 선언되어야 한다. 변수의 선언이란 "이름"과 "타입"을 정해주는 것이다.

 Java 에서 변수의 선언은 Class 내에 선언한다.

 main 함수는 C언어에서는 1개만 존재하지만, Java 에서는 Class 파일마다 존재해도 된다.

 - 메서드 내부에 선언된 변수는 그 메서드 내부에서만 사용 가능하다.

 - 메서드 외부(클래스 내부)에 선언된 변수는 클래스 내에서 사용 가능하다.



생성자(Constructor)는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드다.

- 생성자의 이름은 클래스 이름과 같아야 한다.

- 생성자는 리턴값이 없다.

- 생성자는 매개변수가 없는 것도 있고, 있는 것도 있다.

- 클래스가 객체 생성될 때 맨 처음 호출되는 것이다.

- 모든 클래스는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.

  만약, 생성자가 하나도 정의되어 있지 않는 경우,

  컴파일러는 자동으로 매개변수가 없는(no-parameter) 기본 생성자를 추가하여 컴파일 한다.

- 사용자가 생성자를 구현해주면 no-parameter 생성자는 자동으로 추가되지 않는다.

  생성자를 직접 만들어 줄 경우에는 no-parameter 생성자를 함께 만들어주는 것을 습관화하는 것이 좋다.

- 모든 서브 클래스의 생성자는 먼저 수퍼 클래스의 생성자를 호출한다.

  1. super(매개변수)를 통해 명시적으로 호출을 해준다.

  2. super(매개변수)를 명시적으로 지정하지 않으면 자동으로 no-parameter 생성자가 호출된다.


※ 흔한 오류

    수퍼클래스에 no-parameter 생성자가 없는데, 서브 클래스의 생성자에서 super() 호출을 안해주는 경우


super
- 자신을 가리키는 키워드가 this 라면, 부모를 가리키는 키워드는 super
- super() 는 부모의 생성자를 의미한다.
- 부모의 생성자를 임의로 호출하지 않으면, 부모 class의 기본(no-parameter) 생성자가 자동으로 호출된다.
- 명시적으로 호출하지 않으면 super()를 컴파일 시 자동으로 추가된다.

public class Contacts {
    public Contacts(){  // default 생성자 = no-parameter 생성자     

       // 클래스 인스턴스를 메모리에 생성한다.

       // 주로 인스턴스 변수를 초기화한다.

    }   
}


Contacts pbook = new Contacts(); // default  생성자 호출

default 생성자 호출시 default 생성자가 안 만들어져 있으면 오류가 발생한다.



public class Value {

   // Java에서는 클래스 외부에는 변수를 선언할 수 없다.

    public static final String IPADDRESS = "http://10.100.100.2";  // 서버의 IP주소

    String number;

}


getDbData(Value.IPADDRESS + "/get_json.php");


- 클래스 정적(static) 변수는 객체 생성없이 직접 사용이 가능하다.

- 클래스이름.메서드이름(Value.IPADDRESS) 과 같은 식으로 호출이 가능하다.

- 클래스 정적 변수는 모든 인스턴스가 하나의 저장공간을 가지므로 항상 공통된 값을 갖는다.

  모든 인스턴스에 공통된 값을 유지해야 하는 경우에 static을 붙인다.

  프로그램 실행시 static 지정자가 붙은 멤버들은 단 한번의 실행으로 메모리에 생성된다.

  프로그램 종료 시 메모리에서 소멸된다.


접근 제한자

접근 제한자는 Class 의 멤버에 대해 다른 Class 에서 접근하는 것을 제한하는 용도로 사용된다.

- public : 모든 Class 에서 접근 가능

- protected : 같은 패키지에 있는 Class, 그리고 다른 패키지의 자손 클래스에서 접근이 가능

- default : 같은 패키지에 있는 Class 들만 접근 가능

  String name; // 아무런 접근 지정자도 지정되지 않았으므로 default 접근 지정자다.

- private : 같은 Class 내에서만 접근 가능


지시자

클래스 내부

동일 패키지

상속받은 클래스

이외의 영역

 private


 X

X

X

 default


X

X

 protected


X

 public



- 클래스의 멤버 변수에 접근할 때에는 기본적으로 getter/setter 메서드를 사용하여 간접 접근을 해야 한다.


클래스 상속(inheritance)

public class SearchActivity extends Activity {
}

public class MainActivity extends AppCompatActivity {

       // ....

}

public class Child extends Parent {
}

- 상속이란 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.

- 상속을 받는다는 것은 조상 클래스(Activity)를 확장(extends)한다는 의미로 해석할 수 있다.

  상속은 강한 연결고리를 형성한다.

  복합관계는 강한 연결고리를 형성하지 않는다.

- 자바는 단일 상속만 허용하고 다중 상속은 허용하지 않는다.

  C++ 에서는 여러 클래스로부터 상속받는 다중상속을 허용한다.

  public class Child extends Parent1, Parent2 { // 이와 같은 표현은 허용하지 않는다.

  }

- 모든 상속 계층의 최상위에는 Object 클래스가 위치한다.

- 생성자와 초기화 블럭은  상속되지 않는다. 멤버만 상속된다.

- 클래스 속성과 기능들을 다른 클래스에서 재사용할 수 있다.

- 중복 코드를 제거하면 가독성이 높아지고 개발 시간을 단축할 수 있다.

- 각 클래스의 공통점을 가진 클래스를 부모 클래스로 만든다.


추상(abstract) 클래스

- 미완성 설계도

- 추상 클래스로 인스턴스는 생성할 수 없다.

  추상 클래스는 상속을 통해서 자손 클래스에 의해서만 완성할 수 있다.

- 추상 클래스는 abstract 를 붙이기만 하면 된다.



오버라이딩(overriding)

조상(상위) 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다.

- 상위 클래스에 정의된 이름, 반환형, 매개변수 선언까지 완전히 동일한 메소드를 하위 클래스에서 다시 정의하는 것이다.

- 하위 클래스에 정의된 메소드에 의해 상위 클래스의 메소드는 가리워진다.

overriding 의 조건

- 이름이 같아야 한다.

- 매개변수가 같아야 한다.

- 리턴타입이 같아야 한다.

- static, final, private 가 지정된 메소드는 오바라이딩 불가

  super.toString(); 처럼 부모 클래스 메소드를 호출하거나, private 를 protected 로 변수명을 변경하면 상속이 가능하다.



다형성(Polymorphism)

- 수퍼클래스 타입의 참조변수가 서브클래스 타입의 객체를 참조할 수 있다.

   Computer test = new Notebook();



method overloading

같은 클래스 내에서 변수와 마찬가지로 메소드도 서로 구별할 수 있어야 한다.

하지만 자바에서는 같은 클래스 내에서 아래와 같은 조건이면 동일 이름을 가진 메소드를 정의할 수 있다.

1. 메소드 이름이 같아야 한다.

2. 메소드이 매개변수의 개수 또는 매개변수의 타입이 달라야 한다.

3. 매개변수는 같고 리턴타입이 다른 것은 오버로딩이 성립되지 않는다.

오버로딩의 조건

  1. 메서드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
    (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)

 



출처: https://java.ihoney.pe.kr/99 [허니몬(Honeymon)의 자바guru]

오버로딩의 조건

  1. 메서드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
    (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)

 



출처: https://java.ihoney.pe.kr/99 [허니몬(Honeymon)의 자바guru]


오버로딩의 조건

  1. 메서드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
    (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)


출처: https://java.ihoney.pe.kr/99 [허니몬(Honeymon)의 자바guru]


패키지(package)

package com.tistory.link2me.addresschart;


- 패키지란, 클래스의 묶음이다.

- 하나의 소스파일에는 최상단에 단 한번의 패키지 선언을 허용한다.

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

- 모든 클래스는 반드시 하나의 패키지에 속해야 한다.

- 패키지는 점(.)을 구분자로 하여 계층 구조로 구성할 수 있다.

- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.


import 문

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;


- 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야 한다.

- 하지만, 클래스의 코드를 작성하기 전에 import 문으로 사용하고자 하는 클래스의 패키지를

  미리 명시해 주면 소스코드에 사용되는 클래스 이름에서 패키지명을 생략할 수 있다.


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

[Java] 정보은닉과 캡슐화  (0) 2017.10.16
자바 기본 데이터형과 크기  (0) 2017.10.11
ArrayList and HashMap  (0) 2017.03.06
Java foreach 문  (0) 2017.03.01
자바 배열과 기본 문법  (0) 2016.07.11
블로그 이미지

Link2Me

,
728x90

뷰플리퍼는 여러 개의 뷰를 한 화면에서 보여줄 수 있는 기능을 가진 컨테이너이다.
그래서 여러 개의 뷰를 작성하고 이 뷰들을 한 장 한 장 넘기면서 볼 수 있다.
- 여러 개의 뷰를 한 화면에서 보여줄 수 있다
- 이전, 이후 뷰를 볼 수 있는 메소드가 준비되어 있다


테스트 환경 : Android Studio 2.3.1


view_flipper.xml

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

    <LinearLayout
        android:id="@+id/screenIdx"
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal">
    </LinearLayout>

    <ViewFlipper
        android:id="@+id/flipper"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:padding="16dp"
    tools:context="com.tistory.link2me.viewfliper.MainActivity">

    <com.tistory.link2me.viewfliper.View_Flipper
        android:id="@+id/screen"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp" />

</RelativeLayout>


View_Flipper.java

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ViewFlipper;

public class View_Flipper extends LinearLayout implements View.OnTouchListener {

    public static int cntIndex= 10; // 뷰플리퍼를 구현할 화면의 갯수
    LinearLayout indexLayout; // 현재 화면의 인덱스를 표현하기 위한 레이아웃
    ImageView[] indexImgs; // 현재 화면의 인덱스를 나타내는 이미지들
    View[] views; // 뷰플리퍼에 사용할 뷰
    ViewFlipper viewFlipper;

    float startX; // 손가락으로 화면을 터치했을 때 x좌표값을 저장하기 위한 변수
    float endX; // 화면에서 손가락을 뗏을 때 x좌표값을 저장하기 위한 변수

    int currentIndex = 0; //현재의 화면 인덱스 값

    public View_Flipper(Context context) {
        super(context);
        init(context);
    }

    public View_Flipper(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public void init(Context context){
        setBackgroundColor(0xffbbbbff);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.view_flipper, this, true);

        indexLayout = (LinearLayout) findViewById(R.id.screenIdx);
        viewFlipper = (ViewFlipper) findViewById(R.id.flipper);
        viewFlipper.setOnTouchListener(this);

        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        params.leftMargin = 50;

        indexImgs = new ImageView[cntIndex];
        views = new ImageView[cntIndex];

        //index 이미지와 뷰플리퍼 화면을 만드는 과정
        for(int i=0; i < cntIndex; i++){
            indexImgs[i] = new ImageView(context);

            if (i == currentIndex){
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_on);
            }else {
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_off);
            }

            //indexImgs[i].setPadding();
            indexLayout.addView(indexImgs[i], params);

            ImageView currentView = new ImageView(context);
            currentView.setImageResource(R.mipmap.gametitle_01+i);
            views[i] = currentView;

            viewFlipper.addView(views[i]);
        }
    }

    //인덱스 이미지를 수정
    public void modifyIndex(){
        for(int i = 0; i <cntIndex; i++){
            if(i == currentIndex) {
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_on);
            }else{
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_off);
            }
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(v != viewFlipper) return false;

        if(event.getAction() == MotionEvent.ACTION_DOWN){
            startX = event.getX();
        } else if(event.getAction() == MotionEvent.ACTION_UP){
            endX = event.getX();
            if(startX < endX){ // 왼쪽에서 오른쪽으로 터치
                //viewFlipper에 애니메이션 설정
                viewFlipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.left_in));
                viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.right_out));

                //인덱스체크 - 첫번째화면이면 동작없음
                if(currentIndex > 0){
                    viewFlipper.showPrevious();
                    currentIndex--;
                    modifyIndex();
                }
            } else if(startX > endX){ // 오른쪽에서 왼쪽으로 터치
                viewFlipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.right_in));
                viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.left_out));

                //인덱스체크 - 마지막화면이면 동작없음
                if(currentIndex < (cntIndex-1)){
                    viewFlipper.showNext();
                    currentIndex++;
                    modifyIndex();
                }
            }
        }
        return true;
    }
}


나머지 파일을 포함해서 실행해 볼 수 있는 전체 파일

view_flipper.zip


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

Android Fragment 기본 예제  (0) 2018.09.11
FloatingActionButton(FAB)  (0) 2018.08.15
LinearLayout weight  (0) 2018.03.01
Fragment 화면 이동  (0) 2017.03.26
안드로이드 Layout  (0) 2016.07.23
블로그 이미지

Link2Me

,
728x90

캄보다이 앙코르왓 여행 준비물에 대해 적어본다.


여행기간 : 2017.4.20 ~ 4.25 (3박 5일)

여행지 : 캄보디아 앙코르왓


캄보디아 여행 준비물


여권

- 여권은 나를 식별해주는 신분증이므로 반드시 챙겨야 한다.

출처: http://link2me.tistory.com/1097 [소소한 일상 및 업무TIP 다루기]
- 여권은 나를 식별해주는 신분증이므로 반드시 챙겨야 한다.

출처: http://link2me.tistory.com/1097 [소소한 일상 및 업무TIP 다루기]

- 여권은 나를 식별해주는 신분증이므로 반드시 챙겨야 한다.

- 패키지 여행을 가면 현지 가이드가 여권을 전부 걷었다가 마지막날 공항으로 가는 버스에서 돌려줬다.

- 여행중에 여권을 분실하면 수도 프놈펜까지 비행기로 가야만 여권 발급이 가능하다고 하니

  절대 여권은 분실하지 말아야 한다.

- 여권 분실을 대비해서 사진 2매를 가져가는게 좋다.


신용카드

- 신용카드는 1개 가져가면 된다.

- 쇼핑 가게에 들를 때 필요하더라.

- 쇼핑으로 들른 곳은 상황버섯, 라텍스 가게다.

  라텍스 베게가 정말 편해서 구입했다.

  라텍스는 베트남에서 들여온 것인거 같은데 베트남보다 약간 더 비싸다.

  전세계적으로 베트남산 라텍스가 가장 품질이 좋단다.

- 다른 분들은 상황버섯을 많이 구입하더라.

  가격이 후덜덜해서 난 도저히 구입을 못하겠더라.

  상황버섯으로 끓인 물은 쉬지 않는다고 한번에 1주일치, 한달치 끓이라고 하더라.

  버섯을 냉장고에 넣는 것은 절대 금하라고 알려준다.

- 베트남에서 구입한 사향 족제비 커피(위즐)이 있는지 문의해봤는데 위즐 커피는 없더라.

  발리 사향 고향이 커피(루악)은 있는데, 구입하지 않았다.

  시엠립 공항 면세점에서 파는 커피는 10달러여서 1개만 사서 가져왔다.

 


현금

- 현금은 미리 은행에서 달러로 환전한다. 공항에서 환전하면 손해를 본다.

  캄보디아 여행시 사용하는 돈은 거의 달러라고 보면 된다.

- 1달러, 5달러, 10달러, 20달러, 50달러, 100달러 짜리 등으로 준비한다.

  2달러 짜리는 사용하지 않는단다.

  오래된 10달러 가지고 갔더니 안받아준다.

  다른 나라에서는 사용할 수 있다면서 캄보디아에서는 안된단다.

- 숙소 1달러, 짐 날라다 주면 1달러

  길거리에서 물건파는 애들 1달러 달라고 한다.

  캄보디아는 GDP가 1천달러 수준으로 정말 못사는 나라인지라 1달러의 가치도 엄청 크다.

  성인 1달 월급이 70달러 수준이고 좀 더 많이 받으면 우리나라 돈 기준 15만원 정도 된단다.



비자발급

- 캄보디아는 공항에서 비자 발급을 받아야 한다.

  수수료는 30불인데 공무원들이 1불을 더 줘야 빨리 처리 해준단다.

  그래서 공항 도착하자마자 31불을 제공해야 한다. 입국 비자 서류는 비행기에서 작성한다.

  필기구(볼펜)은 반드시 준비를 해야한다.

- 비자용 사진 한장은 꼭 여권사진이 아니어도 된다.


- 병원에서 진료 받으면 1천달러는 기본으로 깨진다고 하니 비상약은 꼭 준비해야 한다.

  병원에서 오진도 상당히 많이 하는 가 보다. 병원 검사장비가 노후화된 거라 오진이 나온단다.

- 약, 통/제, 제, 드, 모기퇴치제(선택)

- 상처가 생길 경우를 대비해서 연고도 준비하는게 좋다.

- 피곤을 풀어줄 종합비타민제를 준비하는 것도 좋다.

  여행중에는 면역력이 약해질 수 있으므로 비타민을 먹어주면 감기 예방에 도움이 된다.


- 여권은 나를 식별해주는 신분증이므로 반드시 챙겨야 한다.

출처: http://link2me.tistory.com/1097 [소소한 일상 및 업무TIP 다루기]


우산

- 태양이 뜨거우므로 양산을 준비하는 것도 좋다. 날씨가 정말 덥다.

- 우산(3단 접이식)은 준비하는 것도 좋다.

  건기에는 비가 거의 안오므로 우산 용도보다는 양산 용도로 활용하게 되더라.

- 우기에 여행을 한다면 우산은 필수 준비물이 되겠다.


- 관광버스 안에 물을 준비하여 언제든지 먹을 수 있게 해준다. (패키지 여행)

  외국여행하면서 물을 잘못 먹으면 배앓이(장티푸스)를 할 수 있으므로

  현지가이드가 준비해준 물만 먹으라고 하더라.

  후진국이다보니 위생문제 등이 취약한 편이라고 보면 된다.


휴대폰 로밍

- 휴대폰 로밍을 해도 캄보디아는 3G 접속이라 인터넷 속도는 엄청 느리다.

- 충전기는 필수, 보조배터리는 반드시 챙겨가는게 좋다.

  보조배터리는 너무 용량이 큰 것보다 가벼운 걸로 사는게 좋다.

  용량이 너무 큰 것을 사다보니 활용도가 떨어진다.


멀티어댑터 

- 3구 어댑터는 준비하면 밤에 충전할 때 편하다.

- 전기는 220V를 사용한다.


옷, 선크림

- 적도 부근에 위치하여 날씨가 사시사철 여름이므로 날씨가 너무 뜨껍다

- 대부분 반팔을 입고 여행을 한다.

  반팔 + 팔토시를 하면 편하게 여행할 수 있다.

- 얼굴이 타는 걸 방지하기 위해 선크림은 필수다.

  약국에서 파는 4만원 주고 산 선크림은 좋은지 전혀 얼굴이 타지 않았다.

  한번 바르면 하루종일 효과가 간다고 하더라.

- 잠바를 준비해서 비가 올 때 걸치면 좋다.

- 속옷, 양말, 반바지, 긴바지, 반팔

  날씨가 너무 더워 속옥을 갈아 입을 경우가 많으므로 충분히 가져가는게 좋다.

  앙코르왓 사원에 갈 때에는 민소매, 짧은 바지, 치마 등은 입을 수 없고, 무릎을 덮은 긴 옷을 입어야 한다.

- 운동화, 샌들 모두 챙기는 것이 좋다.


기타 준비물

- 마스크

  앙코르왓으로 이동할 때 툭툭이(오토바이를 개조하여 사람이 2명 탑승)로 이동할 때 마스크를 쓰는게 좋다.

  패키지 여행은 현지 가이드가 구매해서 나눠준다.

- 배낭은 가벼운 짐을 넣고 다닐 수 있는 백팩으로 준비한다.
- 비누, 린스/샴푸
  후진국이다보니 호텔이라도 비누 품질은 좋지 않다. 그러므로 비누는 별도로 가져가는게 좋다.

  호텔에 린스가 없더라. 린스는 가져가면 유용하게 사용된다.

- 캄보디아에서 가이드가 나눠주는 부채는 금방 망가진다.

  날씨가 더운 만큼 별도로 부채를 준비하면 좋다.

- 모기퇴치제

  호텔에도 모기가 있더라. 모기퇴치제를 몸에 뿌리고 잠을 잤다.

  약국에서 구입하면 1만원 정도 된다.

- 식사 때문에 고생은 하지 않지만, 김은 가져가면 먹는데 조금 낫다.

 


공항

- 저가 항공을 이용하다보니 무인발급기에서 티켓을 발권 받을 수 없더라.


캄보디아 여행회화

캄보다어 회화는 까로나의 유투브 동영상 https://www.youtube.com/watch?v=hy9MsaKWU8E 을 보고 적었다.

여행하면 현지 가이드가 기본적인 것 몇개는 알려준다.

손의 위치는 가슴에서 코끝 정도까지로 인사를 한다.

- 안녕하세요(잘 지내세요?) : 쏙 써바이, 쏙 써바이 떼

- 잘가 : 쏙 써바이

- 안녕하십니까? : 쭘므리읍 쑤어 (격식있는 인사말)

- 안녕히 계세요 : 쭘므리읍 리어 (격식있는 인사말)

- 감사합니다 : 어 꾼

- 대단히 감사합니다 : 어꾼 쯔라은

- 미안합니다. 실례합니다 : 쏨 또ㅎ

- 천만에요. 괜찮습니다 : 먼 아이 떼, 엇 아이 떼

- 어디 가세요? : 떠으 나?

- 식사 하셨어요? : 냠 바이 하으니 너으?

- 먹었어요 : 냠 하으이

- 아직요, 저 아주 배고파요 : 너으, 크뇸 클리은 나ㅎ

- 다음에 만나요 : 쭈웁 크니어 뻬일 끄라오이

- 다음주에 만나요 : 쭈웁 크니어 아뜯 끄라오이

- 내일 만나요 : 쭈웁 크니어 틍아이 쓰아엑





블로그 이미지

Link2Me

,