'안드로이드 어플'에 해당되는 글 43건

 

클라이언트에게 자신의 메소드를 제공하려면 자신의 메소드 목록을 인터페이스로 정의해야 한다.

 

원격에서 호출되는 메소드는 응용 프로그램의 경계를 넘어서 인수를 전달해야 하는 어려움이 있다.

 

각 응용 프로그램이 사용하는 메모리가 완전히 분리되어 있어 통산의 방법으로는 인수를 넘기기 어렵다.

 

소속된 패키지가 다름은 물론이고 응용 프로그램 수준보다 더 아래쪽의 저수준일 수도 있으며 심지어 자바가 아닌

 

다른 언어일 수도 있다. 따라서 전달할 수 있는 인수의 종류가 자바 기본 타입과 Parcelable 정도로 제한되며 그외에도

 

몇 가지 제약이 존재한다. 안드로이드는 원격 인터페이스를 정의하는 AIDL이라는 별도의 언어를 제공하며

 

AIDL 컴파일러가 인터페이스 정의를 구현하는 스텁까지 생성해준다.

 

AIDL소스를 작성하여 프로젝트에 포함시켜 놓으면 AIDL 컴파일러가 이 인터페이스를 구현하는 자바 파일을 생성하여 gen 폴더에 배치한다.

 

다음 소스는 ICalc 인터페이스에 최소 공배수를 조사하는 메소드와 소수 여부를 조사하는 메소드의 원형을 선언한다.

 

ICalc.aidl

package com.example.ch30_calcservice;

 

//여기서 인터페이스를 선언함

interface ICalc{

    int getLCM(in int a, in int b);

    boolean isPrime(in int n);

}

 

클라이언트에서 서비스에 연결하거나 해제할 때는 다음 메소드를 호출하여 바인딩 한다. 바인딩이란 클라이언트와 서비스를 연결하는

 

동작이다.

 

    boolean bindService(Intent service, ServiceConnection conn, int flags)

    void unbindService(ServiceConnection conn)

 

첫 번째 인수 service는 사용하고자 하는 서비스를 지정하는데 같은 패키지에 있으면 클래스명으로 지정하고 외부에 있다면 서비스의

 

액션명을 사용해야 한다. 두 번째 인수 conn은 서비스가 연결, 해제될 때의 동작을 정의하는 연결 객체이다. 서비스를 사용하는 클라이언트는

 

ServiceConnection 인터페이스를 구현해야 하는데 클라이언트와 서비스가 연결되거나 해제될 때 호출되는 메소드를 정의한다.

 

서비스가 연결될 때 onServiceConnected 메소드가 호출되며 이때 인수로 전달되는 binder 객체를 스텁의 asInterface메소드로 전달하면

 

원격지 서비스 객체를 구할 수 있다.

 

마지막 인수 flag는 서비스 바인딩 방식을 지정하는데 통상 BIND_AUTO_CREATE로 지정하여 서비스를 자동으로 기동시킨다.

 

바인딩이 완료되면 mCalc멤버를 통해 서비스의 메소드를 호출할 수 있다. 그리고 원격지 호출은 예외 발생 가능성이 높으므로 항상

 

예외 블록을 구성해야한다.

 

그리고 다음과 같이 예제를 구성한다.

 

 

CalcService.java

package com.example.ch30_calcservice;

 

import android.app.Activity;

import android.app.Service;

import android.content.Intent;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

 

public class CalcService extends Service {

 

    @Override

    public void onCreate() {

        // TODO Auto-generated method stub

        super.onCreate();

    }

    

    @Override

    public void onDestroy() {

        // TODO Auto-generated method stub

        super.onDestroy();

    }

    

    @Override

    public IBinder onBind(Intent arg0) {

        // TODO Auto-generated method stub

        return mBinder;

    }

 

    //ICalc 인터페이스안에 Stub이라는 추상 클래스가 선언되있음

    ICalc.Stub mBinder = new ICalc.Stub() {

        

        //인터페이스를 구현해주면 .

        @Override

        public boolean isPrime(int n) throws RemoteException {

            // TODO Auto-generated method stub

            int i;//소수여부 판별

            for(i = 2; i < n; i++){

                if(n % i == 0 ) return false;

            }

            return true;

        }

        

        @Override

        public int getLCM(int a, int b) throws RemoteException {

            // TODO Auto-generated method stub

            int i;//최소공배수 구하기

            for(i = 1; ; i++){

                if(i % a == 0 && i % b == 0) break;

            }

            return i;

        }

    };

          

      

      

 

    

}

 

 

calcclient.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    >

<Button

    android:id="@+id/btnLCM"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="최소 공배수 구하기"

    />

<Button

    android:id="@+id/btnPrime"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="소수 조사"

    />

<TextView

    android:id="@+id/result"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:text="계산 결과"

    />

</LinearLayout>

 

 

CalcClient.java

 

package com.example.ch30_calcservice;

 

import android.app.Activity;

import android.content.ComponentName;

import android.content.Intent;

import android.content.ServiceConnection;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

import android.view.View;

import android.widget.TextView;

 

public class CalcClient extends Activity{

    ICalc mCalc;

    TextView mResult;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        // TODO Auto-generated method stub

        super.onCreate(savedInstanceState);

        setContentView(R.layout.calcclient);

        

        mResult = (TextView)findViewById(R.id.result);

    }

    

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btnLCM://버튼별 기능 구현

            int LCM = 0;

            try{

                LCM = mCalc.getLCM(6, 8);

            }catch(RemoteException e){

                e.printStackTrace();

            }

            mResult.setText("6 8 최소 공배수 = " + LCM);

            break;

        case R.id.btnPrime:

            boolean prime = false;

            try{

                prime = mCalc.isPrime(7);

            }catch(RemoteException e){

                e.printStackTrace();

            }

            mResult.setText("7 소수 여부 = " + prime);

            break;

            

        }

    }

    

    //액티비티가 사용자화 상호작용하기 직전에 호출

    @Override

    protected void onResume() {

        // TODO Auto-generated method stub

        super.onResume();

        Intent intent = new Intent(this, CalcService.class);

        bindService(intent, srvConn, BIND_AUTO_CREATE);//서비스 연결

    }

 

    //다른 액티비티가 활성화 되있을 호출

    @Override

    protected void onPause() {

        // TODO Auto-generated method stub

        super.onPause();

        unbindService(srvConn);//서비스 해지

    }

    

    //서비스 연결을 위한 인터페이스 구현

    ServiceConnection srvConn = new ServiceConnection() {

        

        @Override

        public void onServiceDisconnected(ComponentName name) {

            // TODO Auto-generated method stub

            mCalc = null;

        }

        

        @Override

        public void onServiceConnected(ComponentName name, IBinder service) {

            // TODO Auto-generated method stub

            mCalc = ICalc.Stub.asInterface(service);//원격지 서비스 객체를 구함.

        }

    };

}

 

실행 화면

 

최소 공배수를 서비스로 구하기

소수를 서비스로 구하기

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

서비스는 백그라운드에서 실행되며 사용자와 직접적인 상호작용을 하지 않는다.

 

전면에서 사용자를 대면하는 액티비티를 위해 연산이나 메소드 등의 서비스를 제공하는 것이 주된 임무이다.

 

클라이언트에서 어떤 식으로 호출하는 가에 따라 다음 두 가지 방법이 있다.

 

- 백그라운드 데몬 : 배경에서 계속 실행되는 프로세스이다. 클라이언트가 가동시켜 놓기만 하면 사용자의 명령이 없어도 지속적으로

실행된다.

- 원격 호출 인터페이스 : 클라이언트를 위해 특정한 기능을 제공하는 역할을 한다. 자신의 기능을 메소드로 노출시키며 클라이언트는 메소드를 호출함으로써

서비스를 이용한다.

 

사용 용도에 따라 가장 뚜렷한 차이가 발생하는 부분은 서비스의 생명 주기이다.

 

(출처 : http://ccdev.tistory.com/25)

 

백그라운드 서비스의 경우 onStartCommand() 가 호출되고 원격 호출일 때는 클라이언트에게 인터페이스를 노출하는 onBind 메소드가 호출된다.

 

원격 호출로 사용된다면 백그라운드 동작을 준비할 필요가 없으므로 onCreate에서는 최소하느이 초기화만 해 놓고 나머지 초기화는 onBind에서

 

처리한다. 설사 데몬으로 사용되도 onStartCommand에서 실행 준비를 해야하며 onCreate에서 미리 준비할 필요는 없다.

 

    int onStartCommand(Intent intent, int flags, int startId)

intent는 클라이언트가 서비스를 시작할 때 전달한 인텐트이다. 만약 프로세스가 강제 종료된 후 재시작되는 것이라면 이때는 null이 전달된다.

flags는 서비스의 요청에 대한 추가 정보이며 마지막 인수는 서비스 요청에 대한 고유한 식별자이다.

 

차우 stopSelfResult메소드로 서비스가 스스로 종료될 때 이 식별자를 사용한다.

리턴 값은 보통 접착식과 비접착식이 사용되는데 접착식은 시작과 종료가 명시적 비접착식은 명령을 처리하는 동안만 실행됨.

 

서비스는 메인 쓰레드에서 실행되므로 너무 오랜 시간을 끌면 안된다. 즉시 리턴해야되므로 오래 걸리는 작업은 반드시 쓰레드로 분리한다.

 

다음은 데몬으로 동작하는 서비스를 만드는 예제이다.

 

 

NewsService.java

package com.example.ch30_newsservice;

 

import android.app.Service;

import android.content.Intent;

import android.os.Handler;

import android.os.IBinder;

import android.os.Message;

import android.widget.Toast;

 

public class NewsService extends Service {

    boolean mQuit;

    

    @Override

    public void onCreate() {

        // TODO Auto-generated method stub

        super.onCreate();

        //뉴스를 읽기위한 네트워크 접속 초기화코드가 들어갈듯

    }

    

    @Override

    public void onDestroy() {

        // TODO Auto-generated method stub

        super.onDestroy();

        

        //서비스 종료를 알림

        //네트워트 종료 코드가 들어갈듯

        Toast.makeText(this, "Service end", 0).show();

        mQuit = true;

    }

    

    @Override

    public int onStartCommand(Intent intent, int flags, int startId) {

        // TODO Auto-generated method stub

     super.onStartCommand(intent, flags, startId);

      

     //본격적인 백그라운드 동작 시작

     mQuit = false;

     NewsThread thread = new NewsThread(this, mHandler);

     thread.start();//쓰레드 시작

     return START_STICKY;

    

    }

 

    

    //이번 예제는 원격호출을 지원하지않으므로 null 리턴시킴.

    @Override

    public IBinder onBind(Intent arg0) {

        // TODO Auto-generated method stub

        return null;

    }

    

    class NewsThread extends Thread{

        NewsService mParent;

        Handler mHandler;

        String[] arNews = {

                "크리스마스 다가옴",

                "설날 다가옴",

                "s전자 파업",

                "h자동차 파업",

                "가카리 취업 실패",

                "남북통일",

        };

        public NewsThread(NewsService parent, Handler handler){

            mParent = parent;

            mHandler = handler;

        }

        

        //쓰레드 부분

        public void run(){

            for(int idx = 0; mQuit == false;idx++){

                Message msg = new Message();//메시지를 만듬

                msg.what = 0;

                msg.obj = arNews[idx % arNews.length];//값을 넣어줌

                mHandler.sendMessage(msg);//메시지를 보냄

                try{

                    Thread.sleep(5000);//5초마다 메시지를 보냄

                }catch(Exception e){

                    ;

                }

            }

        }

    }

    

    Handler mHandler = new Handler(){//여기서 메시지를 받게 .

        public void handleMessage(android.os.Message msg) {

            if(msg.what == 0){

                String news = (String)msg.obj;

                Toast.makeText(NewsService.this, news, 0).show();//뉴스를 뿌림

            }

            

        };

    };

}

 

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch30_newsservice"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

 

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<!-- enabled 서비스 사용여부 intent-filter부분에는 서비스의 이름을 지정함 -->

         <service android:name="com.example.ch30_newsservice.NewsService" android:enabled="true">

<intent-filter>

<action android:name="com.example.ch30.NEWS"/>

</intent-filter>

</service>

</application>

 

</manifest>

 

 

 

서비스는 백그라운드에서만 동작하며 스스로 기동하지 못하므로 클라이언트가 있어야만 테스트해 볼 수 있다.

클라이언트에서 서비스를 시작 및 중지할 때는 다음 메소드를 호출한다.

    ComponentName startService(Intent service)

    boolean stopService(Intent service)

startService 호출은 중첩되지 않으므로 몇 번을 시작했건 간에 stopService를 한 번만 호출해도 즉시 종료된다.

단 stopService를 호출했더라도 다른 응용 프로그램이 이 서비스를 사용하고 있다면 onDestroy는 호출되지 않는다.

 

방금 작성한 예제에서 다음을 추가하면 된다.

 

newscontroller.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    >

<Button

    android:id="@+id/newsstart"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="뉴스 서비스 시작"

    />

<Button

    android:id="@+id/newsend"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="뉴스 서비스 종료"

    />

</LinearLayout>

 

NewsController.java

 

package com.example.ch30_newsservice;

 

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

 

public class NewsController extends Activity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.newscontroller);

    }

 

    public void mOnClick(View v){

        Intent intent;

        switch(v.getId()){

        case R.id.newsstart:

            intent = new Intent(this, NewsService.class);

            startService(intent);

            break;

        case R.id.newsend:

            intent = new Intent(this, NewsService.class);

            stopService(intent);

            break;

        }

    }

}

 

 

실행 화면

앱을 그냥 실행했을 때

뉴스 서비스 시작 버튼을 누르면 서비스가 시작됨.

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

이전 예제(드래그 섀도우 이미지 변경하기) 를 응용하여 동전 이미지를 드래그 해보는 예제를 만들어보자.

 

이번 예제는 다음과 같이 구성된다.

 

drawable-hdpi.zip(이미지 파일)


  

 

dragcoin.xml

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

    android:layout_width="match_parent"

    android:layout_height="0px"

    android:layout_weight="1"

    android:background="#0000ff"

    >

<ImageView

    android:id="@+id/coin500"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:src="@drawable/coin500" />"

<ImageView

    android:id="@+id/coin100"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:src="@drawable/coin100" />

</LinearLayout>

<LinearLayout

    android:id="@+id/downlinear"

    android:layout_width="match_parent"

    android:layout_height="0px"

    android:layout_weight="1"

    android:background="#00ff00"

    >

<ImageView

    android:id="@+id/coin50"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:src="@drawable/coin50" />

</LinearLayout>

</LinearLayout>

 

 

DragCoin.java

package com.example.ch27_dragcoin;

 

import android.app.Activity;

import android.content.ClipData;

import android.graphics.Canvas;

import android.graphics.Point;

import android.os.Bundle;

import android.view.DragEvent;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

import android.widget.Button;

import android.widget.LinearLayout;

 

 

public class DragCoin extends Activity {

    Button btnSource;

    LinearLayout uplinear, downlinear;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.dragcoin);

    

        // 동전 별로 리스너를 달아줌.

        findViewById(R.id.coin500).setOnTouchListener(mTouchListener);

        findViewById(R.id.coin100).setOnTouchListener(mTouchListener);

        findViewById(R.id.coin50).setOnTouchListener(mTouchListener);    

      

        findViewById(R.id.uplinear).setOnDragListener(mDragListener);

        findViewById(R.id.downlinear).setOnDragListener(mDragListener);

    }

 

      

        View.OnTouchListener mTouchListener = new View.OnTouchListener() {

            

            @Override

            public boolean onTouch(View view, MotionEvent event) {

                // TODO Auto-generated method stub

                //동전을 터치하게 되면

                if(event.getAction() == MotionEvent.ACTION_DOWN){

                    //클립 데이터를 만듬

                    ClipData clip = ClipData.newPlainText("", "");

                    //드래그 시작을 하는데 이때 커스텀 섀도우 이미지를 사용하게 한다.

                    view.startDrag(clip, new CanvasShadow(view,

                            (int)event.getX(), (int)event.getY()), view, 0);//세번째 항목이 view임을 유의

                    view.setVisibility(View.INVISIBLE);//일단 안보이게 .

                    // 동전을 들어 올리는 효과가

                    return true;

                }

                

                return false;

            }

        };

    class CanvasShadow extends View.DragShadowBuilder{

        int mWidth, mHeight;

        int mX, mY;

        public CanvasShadow(View v, int x, int y){

            super(v);

            //좌표를 저장해둠

            mWidth = v.getWidth();

            mHeight = v.getHeight();

            mX = x;

            mY = y;

      

        }

        

        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint){

            shadowSize.set(mWidth, mHeight);//섀도우 이미지 크기 지정함

            shadowTouchPoint.set(mX, mY);//섀도우 이미지 중심점을 지정함.

        }

        

        public void onDrawShadow(Canvas canvas){

            super.onDrawShadow(canvas);//이미지 복사

        }

    }

    

    //드래그 리스너를 선언한다.

    View.OnDragListener mDragListener = new View.OnDragListener() {

        

        @Override

        public boolean onDrag(View v, DragEvent event) {

            // TODO Auto-generated method stub

            switch(event.getAction()){

            case DragEvent.ACTION_DRAG_STARTED:

                return true;

            case DragEvent.ACTION_DRAG_ENTERED:

                return true;

            case DragEvent.ACTION_DRAG_EXITED:

                return true;

            case DragEvent.ACTION_DROP:

                //드래그를 떼었다 놓을

                View view = (View)event.getLocalState();//startDrag에서 3번째 항목을 가져옴

                ViewGroup parent = (ViewGroup)view.getParent();//원래 부모를 구함.(리니어레이아웃)

                parent.removeView(view);//뷰를 제거함

                LinearLayout newparent = (LinearLayout)v;//새로운 부모를 만들고(리니어레이아웃)

                newparent.addView(view);//드롭을 받은 부모에게 뷰를 추가함.

                view.setVisibility(View.VISIBLE);//보이게 .

                return true;

            case DragEvent.ACTION_DRAG_ENDED:

                if(event.getResult() == false){//드래그 종료시 처음 숨겼던 뷰를 다시 보이도록 한다.

                    ((View)(event.getLocalState())).setVisibility(View.VISIBLE);

                }

                return true;

            }

            return true;

        }

    };

}

 

 

실행 화면

첫 실행화면입니다.

백원짜리를 끌면 다음과 같이 원래 백원이 있던 곳이 INVISIBLE이 된다.

아래에 갖다 두면 다음과 같이 백원이 이동이 된다.

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

드래그 섀도우는 드래그 중임을 나타내는 반투명한 이미지이다.

 

드래그를 시작하는 startDrag의 인수로 섀도우 이미지를 생성하는 View.DragShadowBuilder 객체가 전달되며 빌더에 의해 섀도우 이미지가 결정된다.

 

빌더는 다음 2개의 생성자가 있다.

 

    View.DragShadowBuilder(View view)

    View.DragShadowBuilder()

    View getView()

 

view를 인수로 전달하면 뷰와 같은 모양의 섀도우를 생성하며 터치한 곳이 뷰의 중앙에 맞추어진다.

 

생성자로 전달받은 뷰는 언제든지 getView 메소드로 다시 참조할 수 있다. 드래그 대상 뷰의 크기만한 커스텀 섀도우를 그릴 때 뷰의 크기를

 

참조할 수 있고 기타 뷰의 여러 속성을 조사할 수 있다.

 

그러나 디폴트 섀도우는 뷰 외에 별도의 추가 정보를 표시할 수 없으며 터치점이 항상 뷰의 중앙이어서 뷰의 모서리를 드래그할때 약간 어색하다.

 

인수 없는 생성자를 사용하면 대상 뷰를 모르므로 투명한 섀도우를 생성하며 따라서 드래그 중인 뷰가 보이지 않는다.

 

커스텀 섀도우를 만드려면 빌더 클래스를 상속받은 후 다음 메소드를 재정의하여 섀도우를 직접 그려야한다.

 

    void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint)

    void onDrawShadow(Canvas canvas)

 

첫번째 메소드는 섀도우의 크기와 중심점의 자표를 지정한다. 섀도우 이미지를 그릴 때 onDrawShadow 메소드가 호출되며 디폴트 구현은

 

생성자로 받은 뷰를 그리는 것으로 되어 있다. 뷰를 받지 않았거나 직접 그리고 싶다면 인수로 전달된 캔버스 인수에 원하는 그림을 그린다.

 

다음 예제는 노란색 바탕에 빨간색 원으로 드래그 섀도우를 그린다.

 

 

dragshadow.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

<Button

    android:id="@+id/source"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Source" />

<Button

    android:id="@+id/target"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Target" />

</LinearLayout>

 

DragShadow.java

 

package com.example.ch27_dragshadow;

 

import android.app.Activity;

import android.content.ClipData;

import android.content.ClipDescription;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Point;

import android.os.Bundle;

import android.view.DragEvent;

import android.view.View;

import android.widget.Button;

import android.widget.Toast;

 

 

public class DragShadow extends Activity {

    Button btnSource;

    Button btnTarget;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.dragshadow);

        

        btnSource = (Button)findViewById(R.id.source);

        btnSource.setOnLongClickListener(new View.OnLongClickListener() {

            

            @Override

            public boolean onLongClick(View v) {

                // TODO Auto-generated method stub

                //클립데이터를 만듬

                ClipData clip = ClipData.newPlainText("dragtext", "dragtext");

                v.startDrag(clip, new CanvasShadow(v), null, 0);//드래그 시작 커스텀 섀도우 빌더 사용

 

                return false;

            }

        });

      

        btnTarget = (Button)findViewById(R.id.target);

        btnTarget.setOnDragListener(mDragListener);

    }

 

    //커스텀 섀도우 빌더

    class CanvasShadow extends View.DragShadowBuilder{

        int mWidth, mHeight;

        public CanvasShadow(View v){

            super(v);

            mWidth = v.getWidth();//좌표를 가져와서 멤버 변수에 넣어둠.

            mHeight = v.getHeight();//좌표를 가져와서 멤버 변수에 넣어둠.

        }

        

        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint){

            //섀도우의 크기와 중심점의 좌표를 지정하는 메소드임

            shadowSize.set(mWidth, mHeight);//사이즈 지정

            shadowTouchPoint.set(mWidth/3, mHeight/3);//중심점 지정

        }

        

        //섀도우 이미지를 그릴때 메소드가 호출됨

        public void onDrawShadow(Canvas canvas){

            Paint pnt = new Paint();

            pnt.setColor(Color.YELLOW);

            canvas.drawRect(0, 0, mWidth, mHeight, pnt);

            

            Paint pnt2 = new Paint();

            pnt2.setAntiAlias(true);

            pnt2.setColor(Color.RED);

            pnt2.setStrokeWidth(8);

            pnt2.setStyle(Paint.Style.STROKE);//속이빈 도형

            canvas.drawCircle(mWidth/2, mHeight/2, mHeight/2-5, pnt2);//동그라미 그리기

        }

    }

 

View.OnDragListener mDragListener = new View.OnDragListener() {

        

        @Override

        public boolean onDrag(View v, DragEvent event) {

            // TODO Auto-generated method stub

            Button btn;

            //드래그 객체가 버튼인지 확인

            if(v instanceof Button){

                btn = (Button)v;

            }else{

                return false;

            }

            

            

            //이벤트를 받음

            switch(event.getAction()){

            //드래그가 시작되면

            case DragEvent.ACTION_DRAG_STARTED:

                //클립 설명이 텍스트면

                if(event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){

                    btn.setText("Drop OK");//버튼의 글자를 바꿈

                    return true;

                }else{//인텐트의 경우 이쪽으로 와서 드래그를 받을 수가 없다.

                    return false;

                }

            //드래그가 뷰의 경계안으로 들어오면

            case DragEvent.ACTION_DRAG_ENTERED:

                btn.setText("Enter");//버튼 글자 바꿈

                return true;

            //드래그가 뷰의 경계밖을 나가면

            case DragEvent.ACTION_DRAG_EXITED:

                btn.setText("Exit");//버튼 글자 바꿈

                return true;

            //드래그가 드롭되면

            case DragEvent.ACTION_DROP:

                //클립데이터의 값을 가져옴

                String text = event.getClipData().getItemAt(0).getText().toString();

                btn.setText(text);

                return true;

            //드래그 성공 취소 여부에 상관없이 모든뷰에게

            case DragEvent.ACTION_DRAG_ENDED:

                if(event.getResult()){//드래그 성공시

                    Toast.makeText(DragShadow.this, "Drag & Drop 완료", 0).show();

                }else{//드래그 실패시

                    btn.setText("Target");

                }

                return true;

            }

            return true;

        }

    };

}

 

 

실행 화면

첫 실행 화면 Source버튼을 드래그해서 Target버튼에 두겠다.

Source 버튼을 롱터치하면 다음과 같이 노란색 박스와 빨간색 동그라미가 나온다.

드래그가 뷰의 경계안으로 들어오면 버튼 글자를 Enter로 바꾼다.

드래그를 마치면 다음과 같이 버튼의 텍스트가 dragtext로 변한다.

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

드래그 & 드롭은 모바일에서 환경에서 구현하기 위해

 

롱클릭이나 터치 다운 등의 이벤트에서 다음 메소드를 호출한다.

 

    boolean startDrag(ClipData data, View.DragShadowBuilder shadowBuilder, Object myLocalState, int flags)

 

data는 드래그할 데이터이며 클립 데이터 객체를 사용한다. data 안에 교환 대상 데이터는 물론이고 데이터의 특성을 설명하는 메타 데이터도 포함되어 있다.

 

클립보드로 교환할 수 있는 데이터는 드래그 앤 드롭으로도 교환할 수 있다.

 

shadowBuilder는 드래그 중에 보여줄 이미지인 섀도우를 작성하며 시스템은 이 객체의 콜백 메소드를 호출하여 섀도우 이미지를 얻는다.

 

빌더의 생성자로 뷰객체를 전단하면 뷰와 같은 모양의 섀도우를 생성하며 파생한 후 메소드를 재정의하면 임의의 모양으로 그릴 수도 있다.

 

myLocalState는 드래그 앤 드롭 관련 데이터를 가지는 객체이며 드래그 뷰와 드롭 뷰간의 정보 교환에 사용된다.

 

임의의 정보를 전달할 수 있으며 딱히 전달할 대상이 없으면 null로 지정한다. flags는 옵션값이되 현재는 기능이 없으므로 0으로 고정되어 있다.

 

startDrag는 드래그가 정상적으로 시작되었으면 true를 리턴하며 에러로 인해 시작하지 못했으면 false를 리턴한다.

 

startDrag메소드를 호출하여 드래그를 시작하면 시스템은 드래그 중이라는 의미로 섀도우 이미지를 표시하며 손가락이 이동하면 섀도우도 같이 이동한다.

 

드래그 동작 중에 시스템은 관심을 보이는 모든 뷰에게 드래그 이벤트를 보낸다. 드래그 이벤트를 받고 싶은 뷰는 다음 메소드를 호출하여 드래그 리스너를 등록한다.

 

리스너를 등록한 모든 뷰에 대해 onDrag 콜백 메소드가 호출된다.

 

    void View.setOnDragListener(OnDragListener l)

    boolean onDrag(View v, DragEvent event)

 

또는 OnDragEvent 콜백을 재정의할 수도 있는데 콜백을 재정의하려면 뷰를 상속받아야되서 그냥 리스너를 구현하는게 편하다.

 

콜백과 리스너가 둘 다 있으면 리스너가 우선 호출되며 리스너가 false를 리턴해야 다음 순위인 콜백이 호출된다.

 

드래그 이벤트에 관련된 정보는 DragEvent 객체로 전달되며 액션 타입과 데이터가 들어 있다. 액션은 드래그 이벤트의 종류를 지정하며 getAction 메소드로 구한다.

 

드래그를 시작하면 리스너를 등록한 모든 뷰에게 ACTION_DRAG_STARTED 이벤트가 전달된다.

 

계속 이벤트를 받으려면 이 이벤트에 대해 true를 리턴하고 더 이상 드래그에 관심이 없으면 false를 리턴한다. 각 뷰는 DragEvent객체의 메타 데이터를 분석하여 드롭을

 

받을 것인지 결정한다.

 

드래그를 시작한 상태에서 손가락을 계속 움직이면 다음 3개의 이벤트가 손가락 아래의 뷰에게 전달된다.

 

이벤트

설명

ACTION_DRAG_ENTERED 

뷰의 경계 안으로 들어왔다. 이벤트를 계속 받으려면 true를 리턴해야 한다.

ACTION_DRAG_LOCATION 

경계 안에서 이동 중이다.

ACTION_DRAG_EXITED 

경계를 벗어났다.

 

뷰는 이 이벤트를 받았을 때 드롭 가능하다는 것을 표시하기 위해 뷰의 모양을 변경한다 뷰 영역 안에서 손가락을 놓으면 섀도우 이미지는 제거되며 드래그 동작이

 

종료된다. 이때 손가락 아래의 뷰에게 ACTION_DROP 이벤트가 전달되며 이때 다음 메소드로 드래그 대상 데이터와 뷰 내에서 드롭된 좌표를 구한다.

 

    ClipData getClipData()

    ClipDescription getClipDescription()

    float getX()

    float getY()

 

드롭을 받은 뷰는 이 메소드로 드래그된 데이터를 구해 사용한다. 클립 데이터에서 실제 데이터를 꺼내는 방법은 클립보드의 경우와 같다. 설명의 마임 타입을 점검해 보고

 

원하는 타입에 대해서만 드롭을 받아야한다. 드롭을 받아 정상적으로 처리했으면 true를 리턴하고 그렇지 않으면 false를 리턴한다.

 

드롭 처리까지 끝나고 드래그가 완전히 종료되면 모든 뷰에게 ACTION_DRAG_ENDED 이벤트가 전달된다. ACTION_DRAG_STARTED 이벤트에 대해 false를 리턴한 뷰와 현재 숨겨진

 

뷰에게까지 종료 이벤트는 전달된다. 종료 이벤트에서 getResult 메소드는 드롭 동작이 제대로 수정되었는지 조사한다.

 

드래그 중에 모양을 변경한 뷰는 종료 이벤트를 받았을 때 원래 모습으로 복귀한다.

 

다음은 드래그 앤 드롭 예제이다.

 

 

위와 같이 2개의 파일을 수정해야 한다.

 

dragbutton.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

<Button

    android:id="@+id/source"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Source" />

<Button

    android:id="@+id/source2"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Intent" />

<Button

    android:id="@+id/target"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Target" />

<Button

    android:id="@+id/nodrop"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="No Drop" />

</LinearLayout>

 

DragButton.java

 

package com.example.ch27_dragbutton;

 

import android.app.Activity;

import android.content.ClipData;

import android.content.ClipDescription;

import android.net.Uri;

import android.os.Bundle;

import android.view.DragEvent;

import android.view.View;

import android.widget.Button;

import android.widget.Toast;

 

public class DragButton extends Activity {

    Button btnSource;

    Button btnTarget;

      

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.dragbutton);

      

        btnSource = (Button)findViewById(R.id.source);

        //버턴의 롱클릭 리스너 등록

        btnSource.setOnLongClickListener(new View.OnLongClickListener() {

            

            @Override

            public boolean onLongClick(View v) {

                // TODO Auto-generated method stub

                //롱클릭시 클립데이터를 만듬

                ClipData clip = ClipData.newPlainText("dragtext", "dragtext");

                //드래그할 데이터, 섀도우 지정, 드래그 드롭 관련 데이터를 가지는 객체 지정, 0

                v.startDrag(clip, new View.DragShadowBuilder(v), null, 0);

                return false;

            }

        });

        

        Button btnSource2 = (Button)findViewById(R.id.source2);

        btnSource2.setOnLongClickListener(new View.OnLongClickListener() {

            

            @Override

            public boolean onLongClick(View v) {

                // TODO Auto-generated method stub

                ClipData clip = ClipData.newRawUri("uri",

                        Uri.parse("content://com.example.ch20_contentprovider/word/boy"));

                v.startDrag(clip, new View.DragShadowBuilder(v), null, 0);

                return false;

            }

        });

        

        btnTarget = (Button)findViewById(R.id.target);

        btnTarget.setOnDragListener(mDragListener);

        

    }

    

    View.OnDragListener mDragListener = new View.OnDragListener() {

        

        @Override

        public boolean onDrag(View v, DragEvent event) {

            // TODO Auto-generated method stub

            Button btn;

            //드래그 객체가 버튼인지 확인

            if(v instanceof Button){

                btn = (Button)v;

            }else{

                return false;

            }

            

            

            //이벤트를 받음

            switch(event.getAction()){

            //드래그가 시작되면

            case DragEvent.ACTION_DRAG_STARTED:

                //클립 설명이 텍스트면

                if(event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){

                    btn.setText("Drop OK");//버튼의 글자를 바꿈

                    return true;

                }else{//인텐트의 경우 이쪽으로 와서 드래그를 받을 수가 없다.

                    return false;

                }

            //드래그가 뷰의 경계안으로 들어오면

            case DragEvent.ACTION_DRAG_ENTERED:

                btn.setText("Enter");//버튼 글자 바꿈

                return true;

            //드래그가 뷰의 경계밖을 나가면

            case DragEvent.ACTION_DRAG_EXITED:

                btn.setText("Exit");//버튼 글자 바꿈

                return true;

            //드래그가 드롭되면

            case DragEvent.ACTION_DROP:

                //클립데이터의 값을 가져옴

                String text = event.getClipData().getItemAt(0).getText().toString();

                btn.setText(text);

                return true;

            //드래그 성공 취소 여부에 상관없이 모든뷰에게

            case DragEvent.ACTION_DRAG_ENDED:

                if(event.getResult()){//드래그 성공시

                    Toast.makeText(DragButton.this, "Drag & Drop 완료", 0).show();

                }else{//드래그 실패시

                    btn.setText("Target");

                }

                return true;

            }

            return true;

        }

    };

 

    

}

 

 

실행 화면

  실행하면 다음과 같이 버튼 4개가 보입니다.


Source 버튼을 끌면 Target 이 Drop OK로 바뀐다.



끌어다 놓으면 dragtext로 바뀐다.



Intent버튼을 끌어다 놓으면 변화가 없다.


블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

클립보드에 들어가는 데이터가 인텐트라는 것만 다를 뿐 텍스트 복사와 별 차이가 없다.

 

예제는 다음과 같이 구성된다.

 

 

 

copyintent.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

<Button

    android:id="@+id/btncopy"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Copy" />

<Button

    android:id="@+id/btnpaste"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Paste" />

</LinearLayout>

 

CopyIntent.java

package com.example.ch27_copyintent;

 

import android.app.Activity;

import android.content.ClipData;

import android.content.ClipDescription;

import android.content.ClipboardManager;

import android.content.Context;

import android.content.Intent;

import android.net.Uri;

import android.os.Bundle;

import android.view.View;

import android.widget.Toast;

 

 

public class CopyIntent extends Activity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.copyintent);

    }

 

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btncopy:

            copyIntent();

            break;

        case R.id.btnpaste:

            pasteIntent();

            break;

        }

    }

    

    void copyIntent(){

        //클립보드 매니저를 가져옴

        ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

        //인텐트를 만듬 Uri 넣어줌

        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

        //클립 데이터 생성

        ClipData clip = ClipData.newIntent("intent", intent);

        cm.setPrimaryClip(clip);//클립보드에 넣음

        Toast.makeText(this, "Intent Copied", 0).show();

    }

    

    void pasteIntent(){

        //클립보드 매니저를 가져옴

        ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

        

        //클립보드가 비었는지 확인

        if(cm.hasPrimaryClip() == false){

            Toast.makeText(this, "Clipboard Empty", 0).show();

            return;

        }

        //클립보드가 INTENT 저장하고 있는지 확인

        if(cm.getPrimaryClipDescription().hasMimeType(

                ClipDescription.MIMETYPE_TEXT_INTENT) == false){

            Toast.makeText(this, "Clip is not intent", 0).show();

            return;

        }

      

        //클립데이터를 가져옴

        ClipData clip = cm.getPrimaryClip();

        ClipData.Item item = clip.getItemAt(0);

        Intent intent = item.getIntent();//아이템에서 INTENT 가져옴

        if(intent != null){

            startActivity(intent);//인텐트를 시작함

        }

    }

      

    

}

 

 

실행 화면

copy를 누르면 인텐트 클립데이터를 클립보드에 넣는다.

paste버튼을 누르면 클리보드에서 인텐트를 가져와 인텐트를 시작한다.

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

이미지나 구조체 같은 복잡한 데이터는 URI를 통해 간접적으로 복사한다.

 

붙여 넣는 쪽에서는 URI를 먼저 구하고 CP에게 데이터를 요청하여 실제 데이터를 구하는 방시긍ㄹ 쓴다.

 

URI로 데이터를 전달하려면 주는 쪽에서는 이 URI에 반응할 수 있는 CP를 반드시 제공해야 한다.

 

다음 예제를 실행하려면 콘텐트프로바이더 예제를 먼저 실행 시켜서 insert버튼을 눌러둔다..

 

또한 위의 콘텐트프로바이더 예제의 매니페스트 파일에서 다음과 같이 grantUriPermissions를 선언해야 한다.

안그러면 Permission 에러가 발생한다.( 여기 참고)

 

<!-- 위에서 추가한 퍼미션은 아래와 같이 사용된다 -->

<provider

android:name="EWProvider"

android:authorities="com.example.ch20_contentprovider"

android:exported="true"

android:readPermission="de.test.READ_DATABASE"

android:writePermission="de.test.WRITE_DATABASE"

android:grantUriPermissions="true"

     ></provider>

 

 

 

 

위와 같이 수정해야 할 것은 3군데이다.

 

다음은 AndroidManifest.xml 파일이다.

여기서 유의할 점은 ContentProvider를 사용하기위해서 퍼미션을 주는 것이다.

아래의 퍼미션은 ContentProvider를 구현한 앱의 매니페스트 파일에서 확인 할 수 있다.

 

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch27_copyuri"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

 

<!-- ContentProvider 구현한 앱에서 정의한 퍼미션을 그대로 써줘야한다. -->

<uses-permission android:name="de.test.READ_DATABASE" />

    <uses-permission android:name="de.test.WRITE_DATABASE" />

 

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".CopyUri"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

 

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

</intent-filter>

</activity>

</application>

 

</manifest>

 

다음은 copyuri.xml 파일이다.

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

<Button

    android:id="@+id/btncopy"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Copy" />

<TextView

    android:id="@+id/pastetext1"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="paste here" />

<Button

    android:id="@+id/btnpasteuri"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Paste" />

<TextView

    android:id="@+id/pastetext2"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="paste here" />

<Button

    android:id="@+id/btnpastetext"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Paste Text" />

</LinearLayout>

 

다음은 CopyUri.java 파일이다.

 

package com.example.ch27_copyuri;

 

import android.app.Activity;

import android.content.ClipData;

import android.content.ClipDescription;

import android.content.ClipboardManager;

import android.content.ContentResolver;

import android.content.Context;

import android.database.Cursor;

import android.net.Uri;

import android.os.Bundle;

import android.view.View;

import android.widget.TextView;

import android.widget.Toast;

 

public class CopyUri extends Activity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.copyuri);

    }

 

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btncopy:

            copyUri();

            break;

        case R.id.btnpasteuri:

            pasteUri();

            break;

        case R.id.btnpastetext:

            pasteUriText();

            break;

        }

    }

    

    void copyUri(){

        ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

        Uri copyuri = Uri.parse("content://com.example.ch20_contentprovider/word/boy");

        

        //클립데이터를 만듬

        ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyuri);

        //클립데이터를 저장함.

        cm.setPrimaryClip(clip);

        Toast.makeText(this, "Uri Copied", 0).show();

        

    }

    

    void pasteUri(){

        ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

        //클립데이터가 있는지 조사해봄

        if(cm.hasPrimaryClip() == false){

            Toast.makeText(this, "Clipboard Empty", 0).show();

            return;

        }

        

        //클립보드에 저장된 데이터에 대한 설명을 가져옴

        //URI 목록인지 확인함

        if(cm.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_URILIST) == false){

            Toast.makeText(this, "Clip is not uri", 0).show();

            return;

        }

        

        //클립 데이터를 가져옴

        ClipData clip = cm.getPrimaryClip();

        //클립 데이터를 여기서 가져옴 0번째가 보통 저장된 곳임.

        ClipData.Item item = clip.getItemAt(0);

        

        Uri pasteuri = item.getUri();//Uri 가져옴

        //CP 사용하기 위한 콘텐트리졸버를 선언

        ContentResolver cr = getContentResolver();

        String uriMime = cr.getType(pasteuri);//Content Provider 이미 getType 정의해둠

        //아래 MIME 맞는지 비교 보안을 위한 것임

        if(uriMime == null || uriMime.equals("vnd.EnglishWord.andexam.cursor.dir/word")==false){

            Toast.makeText(this, "Clip is not EnglishWord", 0).show();

        }

        

        //CP에서 정의한 query문을 보면 이해가 것임

        Cursor pastecursor = cr.query(pasteuri, null, null, null, null);

        

        //쿼리에 올린 값을 실제로 가져오는 부분

        if(pastecursor != null){

            if(pastecursor.moveToFirst()){

                TextView pastetext = (TextView)findViewById(R.id.pastetext1);

                //데이터베이스에서 1번째 컬럼과 2번째 컬럼 값을 가져옴

                pastetext.setText(pastecursor.getString(0) + ":" + pastecursor.getString(1));

            }

            //커서를 닫음

            pastecursor.close();

        }else{//없으면

            Toast.makeText(this, "Data not found", 0).show();

        }

        

    }

    

    void pasteUriText(){

        //클립보드 매니저를 선언함.

        ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

        //클립보드가 비었으면?

        if(cm.hasPrimaryClip() == false){

            Toast.makeText(this, "Clipboard Empty", 0).show();

            return;

        }

      

        //클립데이터를 만듬

        ClipData clip = cm.getPrimaryClip();

        ClipData.Item item = clip.getItemAt(0);//데이터를 가져옴

        TextView pastetext = (TextView)findViewById(R.id.pastetext2);

        //coerceToText 타입에 상관없이 강제로 문자열 형태로 바꿔읽는다.

        //그다음 텍스트뷰에 출력함.

        pastetext.setText(item.coerceToText(this).toString());

        

    }

 

}

 

 

실행화면

 

Copy 버튼을 누른뒤

 

붙여넣기를 하면 다음과 같이 된다.

 

다음화면에서 boy:머스마 는 Pate 버튼을 누른것이고 Paste Text버튼을 누르면 위의 content://~~가 보인다.

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

1. URI

 

안드로이드의 보안 정책상 응용 프로그램이 만든 데이터는 기본적으로 혼자만 액세스 가능하다. 자신이 만든 데이터를 외부로 공개할 때는 Content Provider를 제공해야한다.

 

콘텐트 프로바이더는 응용 프로그램을 구성하는 컴포넌트 중 하나로서 데이터를 제공하는 역할을 한다.

 

URI(Uniform Resource Identifier)는 웹상의 주소를 나타내는 URL보다 더 상위의 개념이다. 국제 표준에 URI를 작성하는 방식은 다음과 같이 명시되어 있다.

 

    content://authority/path/id

 

content://는 이 문자열이 URI임을 나타내는 접두이며 무조건 붙여야 한다. authority는 정보 제공자의 명칭이되 중복되면 안 되므로 패키지명을 사용할 것을 권장한다.

 

path는 정보의 종류를 지정하는 가상의 경로이다. id는 어떤 정보를 원하는지 지정하되 전체 정보를 다 읽을 때는 생략할 수 있다.

 

CP는 단수와 복수에 대해 두 가지 형태의 URI를 각각 정의해야 한다.

 

id까지만 있으면 단수이고 path 까지만 있으면 복수이다. 다음은 URI의 몇 가지 예로서 stockmarket이라는 가상의 회사에서 제공하는 정보의 예이다.

 

    content://com.stockmarket/stock //주식 정보

    content://com.stockmarket/stock/posco //posco 종목의 주식 정보

 

URI는 항상 content://로 시작하고 이어서 정보 제공자를 밝히는 저작권 정보가 온다. com.stockmarket이라는 자사의 URL을 사용했으므로 웬만해서는 제공자가 중복되지 않을 것이다.

 

정보 제공자와 정보의 종류에 따라 이 형식대로 URI 문자열을 만든 후 다음 정적 메소드로 URI 객체를 생성한다.

 

    static Uri parse(String uriString)

 

이 메소드는 성능상의 문제로 인해 에러 처리는 하지 않는다. 즉 URI를 잘못 작성하면 쓰레기값이 조사된다.

 

다음 메소드는 URI의 path 정보를 문자열 목록으로 조사한다.

 

    List<String> getPathSegments()

 

이 목록의 0번째 요소가 path이며 1번째 요소가 id이되 /를 제외한 문자열만 조사된다.

 

문자열을 비교해서는 속도가 느리므로 UriMatcher라는 유틸리티 클래스를 이용해서 쉽게 문자열을 비교 할 수 있다.

 

    void addURI(String authority, String path, int code)

    int match(Uri uri)

 

addURI메소드로 authority, path의 쌍으로 정수 코드와 대응시켜 맵을 등록한다. path 에서 *는 임의의 문자열과 대응되며 #은 숫자 하나와 대응된다.

match 메소드는 uri를 분석하여 등록된 정수 코드를 리턴한다. 만약 uri에 해당하는 코드가 발견되지 않으면 -1을 리턴한다.

 

CP는 클라이언트의 URI를 일일이 분석할 필요없이 UriMatcher가 분석해 놓은 정수 코드로 요청을 파악하여 정보를 리턴하면 된다.

정수로부터 요청을 구분할 수 있으므로 switch case문으로 분기할 수 있어 편리하고 코드도 깔끔하게 정리된다.

 

2. 자료공유

 

CP를 만들기 위해서 먼저 ContentProvider클래스를 상속받아야 하며 정보를 관리 및 제공하는 메소드를 재정의해야 한다.

 

onCreate는 CP가 로드될 때 호출되는데 여기서 제공할 데이터를 준비한다.

 

예를 들어 데이터베이스에 들어 있는 정보라면 onCreate에서 DB를 열어 두면 된다. 다음 메소드는 제공하는 데이터의 MIME 타입을 조사한다.

 

    String getType(Uri uri)

 

대체로 다음 형식대로 작성한다.

 

    1. 단수 : vnd.회사명.cursor.item/타입

    2. 복수 : vnd.회사명.cursor.dir/타입

 

CP는 원래 기능을 그대로 유지한 채로 고유의 데이터를 외부에 공개하는 컴포넌트를 하나 더 추가하는 것이므로 기존의 액티비티는 전혀 건드릴 필요가 없다.

 

1. CP를 만드는 예제

 

위와 같이 3부분을 수정해야 합니다.

 

AndroidManifest.xml

application 태그 아래, Provider 태그를 추가한다. 시스템은 정보를 요청하는 쪽의 URI와 매니페스트의 URI를 비교해보고 제공자가 일치하는 CP를 호출한다.

 

그리고 반드시 퍼미션을 정의해줘야 한다. 안그러면 Permission denial: opening provider 에러가 발생한다. (다음 링크 참고 : http://stackoverflow.com/questions/14368867/permission-denial-opening-provider)

 

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch20_cp_use"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

<!-- ContentProvider 구현한 앱에서 정의한 퍼미션을 그대로 써줘야한다. -->

<uses-permission android:name="de.test.READ_DATABASE" />

    <uses-permission android:name="de.test.WRITE_DATABASE" />

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".CallWordCP"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

 

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

</intent-filter>

</activity>

 

</application>

 

 

 

</manifest>

 

 

englishword.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    >

<Button

    android:id="@+id/insert"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Insert"

    />

<Button

    android:id="@+id/delete"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Delete"

    />

<Button

    android:id="@+id/update"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Update"

    />

<Button

    android:id="@+id/select"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Select"

    />

<EditText

    android:id="@+id/edittext"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    />

</LinearLayout>

 

 

EnglishWord.java

 

package com.example.ch20_contentprovider;

 

import android.app.*;

import android.content.*;

import android.database.*;

import android.database.sqlite.*;

import android.os.*;

import android.view.*;

import android.widget.*;

 

public class EnglishWord extends Activity {

    WordDBHelper mHelper;

    EditText mText;

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.englishword);

 

        mHelper = new WordDBHelper(this);

        mText = (EditText)findViewById(R.id.edittext);

    }

    

    public void mOnClick(View v) {

        SQLiteDatabase db;

        ContentValues row;

        switch (v.getId()) {

        case R.id.insert:

            db = mHelper.getWritableDatabase();

            // insert 메서드로 삽입

            row = new ContentValues();

            row.put("eng", "boy");

            row.put("han", "머스마");

            db.insert("dic", null, row);

            // SQL 명령으로 삽입

            db.execSQL("INSERT INTO dic VALUES (null, 'girl', '가시나');");

            mHelper.close();

            mText.setText("Insert Success");

            break;

        case R.id.delete:

            db = mHelper.getWritableDatabase();

            // delete 메서드로 삭제

            db.delete("dic", null, null);

            // SQL 명령으로 삭제

            //db.execSQL("DELETE FROM dic;");

            mHelper.close();

            mText.setText("Delete Success");

            break;

        case R.id.update:

            db = mHelper.getWritableDatabase();

            // update 메서드로 갱신

            row = new ContentValues();

            row.put("han", "소년");

            db.update("dic", row, "eng = 'boy'", null);

            // SQL 명령으로 갱신

            //db.execSQL("UPDATE dic SET han = '소년' WHERE eng = 'boy';");

            mHelper.close();

            mText.setText("Update Success");

            break;

        case R.id.select:

            db = mHelper.getReadableDatabase();

            Cursor cursor;

            // query 메서드로 읽기

            //cursor = db.query("dic", new String[] {"eng", "han"}, null,

            //        null, null, null, null);

            // SQL 명령으로 읽기

            cursor = db.rawQuery("SELECT eng, han FROM dic", null);

        

            String Result = "";

            while (cursor.moveToNext()) {

                String eng = cursor.getString(0);

                String han = cursor.getString(1);

                Result += (eng + " = " + han + "\n");

            }

 

            if (Result.length() == 0) {

                mText.setText("Empyt Set");

            } else {

                mText.setText(Result);

            }

            cursor.close();

            mHelper.close();

            break;

        }

    }

}

 

//여기서 SQLite 데이터 베이스를 만든다.

class WordDBHelper extends SQLiteOpenHelper {

    public WordDBHelper(Context context) {

        super(context, "EngWord.db", null, 1);

    }

 

    public void onCreate(SQLiteDatabase db) {

        db.execSQL("CREATE TABLE dic ( _id INTEGER PRIMARY KEY AUTOINCREMENT, " +

        "eng TEXT, han TEXT);");

    }

 

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        db.execSQL("DROP TABLE IF EXISTS dic");

        onCreate(db);

    }

}

 

 

EWProvider.java

 

package com.example.ch20_contentprovider;

 

import android.content.ContentProvider;

import android.content.ContentUris;

import android.content.ContentValues;

import android.content.UriMatcher;

import android.database.Cursor;

import android.database.sqlite.SQLiteDatabase;

import android.net.Uri;

import android.text.TextUtils;

 

public class EWProvider extends ContentProvider{

    static final Uri CONTENT_URI = Uri.parse("content://com.example.ch20_contentprovider/word");

    

    static final int ALLWORD = 1;

    static final int ONEWORD = 2;

    

    static final UriMatcher Matcher;

    static {

        Matcher = new UriMatcher(UriMatcher.NO_MATCH);

        Matcher.addURI("com.example.ch20_contentprovider", "word", ALLWORD);

        Matcher.addURI("com.example.ch20_contentprovider", "word/*", ONEWORD);

    }

    

    SQLiteDatabase mDB;

    

    //콘텐트 프로바이더 객체가 생성되면 호출됨.

    @Override

    public boolean onCreate() {

        // TODO Auto-generated method stub

        //공유할 데이터베이스를 열어둔다.

        WordDBHelper helper = new WordDBHelper(getContext());

        mDB = helper.getWritableDatabase();

        

        return true;

    }

    

    //MIME 타입을 정의해둠

    //클라리언트에서 주어진 uri 보고 ContentProvider 제공하는 데이터의 MIME타입을 리턴해준다.

    //특정한 상황에서만 사용

    @Override

    public String getType(Uri uri) {

        // TODO Auto-generated method stub

        if(Matcher.match(uri) == ALLWORD){

            return "vnd.EnglishWord.andexam.cursor.item/word";

        }

        if(Matcher.match(uri) == ONEWORD){

            return "vnd.EnglishWord.andexam.cursor.dir/word";

        }

        

        return null;

    }

 

    //실제 데이터를 제공하는 부분

    @Override

    public Cursor query(Uri uri, String[] projection, String selection,

            String[] selectionArgs, String sortOrder) {

        // TODO Auto-generated method stub

        String sql;

        

        //전체에 대한 쿼리 명령

        sql = "SELECT eng, han FROM dic";

        

        //단어 선택 where 추가

        //인수로 전달된 uri 분석함

        if(Matcher.match(uri) == ONEWORD){

            sql += " where eng = '" + uri.getPathSegments().get(1) + "'";//get(1)에서 1 id 의미

        }

        

        Cursor cursor = mDB.rawQuery(sql, null);//sql 실행

        

        return cursor;

    }

 

    // 하나를 삽입한다.

    public Uri insert(Uri uri, ContentValues values) {

        long row = mDB.insert("dic", null, values);

        

        //입력에 성공하면 notifyChange 메소드를 호출함.

        if (row > 0) {

            //아래는 추가된 ID 보유한 Uri 객체를 리턴하는 메소드

            //관련 링크 : http://posnopi13.tistory.com/19 참고

            Uri notiuri = ContentUris.withAppendedId(CONTENT_URI, row);

            

            //다른 CP들에게 변화를 알림

            getContext().getContentResolver().notifyChange(notiuri, null);

            return notiuri;

        }

        return null;

    }

 

    //

    public int delete(Uri uri, String selection, String[] selectionArgs) {

        int count = 0;

        

        //단수와 복수에 대한 요청 구분

        switch (Matcher.match(uri)) {

        case ALLWORD://복수 요청의 경우

            count = mDB.delete("dic", selection, selectionArgs);

            break;

        case ONEWORD://단수 요청의 경우

            String where;

            //해당하는 것만 지우기 위해 쿼리문을 만듬

            where = "eng = '" + uri.getPathSegments().get(1) + "'";

            //String null체크를 하기위한 메소드 빈문자열경우true 리턴

            if (TextUtils.isEmpty(selection) == false) {

                where += " AND" + selection;

            }

            count = mDB.delete("dic", where, selectionArgs);

            break;

        }

        

        //변화된것을 알림

        getContext().getContentResolver().notifyChange(uri, null);

        return count;

        //*/

        

        /* 아래와같이 구현해도됨

        String sql;

        

        // 전체에 대한 쿼리 명령

        sql = "DELETE FROM dic";

        

        // 단어 선택 where 추가

        if (Matcher.match(uri) == ONEWORD) {

            sql += " where eng = '" + uri.getPathSegments().get(1) + "'";

        }

        mDB.execSQL(sql);

        return 1;

        //*/

    }

 

    public int update(Uri uri, ContentValues values, String selection,

            String[] selectionArgs) {

        int count = 0;

        

        switch (Matcher.match(uri)) {

        case ALLWORD:

            count = mDB.update("dic", values, selection, selectionArgs);

            break;

        case ONEWORD:

            String where;

            where = "eng = '" + uri.getPathSegments().get(1) + "'";

            //delete쪽과 같음

            if (TextUtils.isEmpty(selection) == false) {

                where += " AND " + selection;

            }

            count = mDB.update("dic", values, where, selectionArgs);

            break;

        }

        

        getContext().getContentResolver().notifyChange(uri, null);

        return count;

    }

}

 

실행 화면

insert 버튼을 누르면 데이터베이스에 추가되고 delete버튼을 누르면 데이터베이스에서 삭제된다.

그리고 select버튼을 누르면 값이 출력된다. update는 머스마를 소년으로 바꾼다.

 

2. CP를 사용하는 예제

 

AndroidManifest.xml

 

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch20_cp_use"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

<!-- ContentProvider 구현한 앱에서 정의한 퍼미션을 그대로 써줘야한다. -->

<uses-permission android:name="de.test.READ_DATABASE" />

    <uses-permission android:name="de.test.WRITE_DATABASE" />

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".CallWordCP"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

 

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

</intent-filter>

</activity>

 

</application>

 

 

 

</manifest>

 

 

callwordcp.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    >

<Button

    android:id="@+id/readall"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Readl All"

    />

<Button

    android:id="@+id/readone"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Read One"

    />

<Button

    android:id="@+id/insert"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Insert"

    />

<Button

    android:id="@+id/delete"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Delete"

    />

<Button

    android:id="@+id/update"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"

    android:text="Update"

    />

<EditText

    android:id="@+id/edittext"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    />

</LinearLayout>

 

 

CallWordCP.java

 

package com.example.ch20_cp_use;

 

import android.app.Activity;

import android.content.ContentResolver;

import android.content.ContentValues;

import android.database.Cursor;

import android.net.Uri;

import android.os.Bundle;

import android.view.View;

import android.widget.EditText;

 

public class CallWordCP extends Activity {

    static final String WORDURI = "content://com.example.ch20_contentprovider/word";

    EditText mText;                

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.callwordcp);

        

        mText = (EditText)findViewById(R.id.edittext);

    }

    

    public void mOnClick(View v){

        

        //먼저 ContentResolver 객체를 구한다.

        //리졸버를 쿼리를 통해 cp 간접적으로 통신하는 표준 인터페이스이다.

        ContentResolver cr = getContentResolver();

        

        switch(v.getId()){

        //전부 읽기

        case R.id.readall:

            Cursor cursor = cr.query(Uri.parse(WORDURI), null, null, null, null);

        

            String Result = "";

            //데이터 베이스 값을 가져와서 뿌려줌

            while(cursor.moveToNext()){

                String eng = cursor.getString(0);

                String han = cursor.getString(1);

                Result += (eng + " = " + han + "\n");

            }

            

            if(Result.length() == 0){

                mText.setText("Empty set");

            }else{

                mText.setText(Result);

            }

            

            cursor.close();

            break;

            

            //하나만 읽기

            case R.id.readone:

                // /boy인것만 읽어옴

                Cursor cursor2 = cr.query(Uri.parse(WORDURI + "/boy"),

                        null, null, null, null);

                

                String Result2 = "";

                if(cursor2.moveToFirst()){

                    String eng = cursor2.getString(0);

                    String han = cursor2.getString(1);

                    Result2 += (eng + " = " + han + "\n");

                }

                

                if(Result2.length() == 0){

                    mText.setText("Empty Set");

                }else{

                    mText.setText(Result2);

                }

                cursor2.close();

                break;

                

            //삽입

            case R.id.insert:

                ContentValues row = new ContentValues();

                row.put("eng", "school");

                row.put("han", "학교");

                cr.insert(Uri.parse(WORDURI), row);

                mText.setText("Insert Success");

                break;

                

            //삭제

            case R.id.delete:

                cr.delete(Uri.parse(WORDURI), null, null);

                mText.setText("Delete Success");

                break;

                

            //수정

            case R.id.update:

                ContentValues row2 = new ContentValues();

                row2.put("han", "핵교다");

                cr.update(Uri.parse(WORDURI + "/school"), row2, null, null);

                mText.setText("Update Success");

                break;

        }

        

    }

    

 

}

 

 

실행 화면

CP를 구현한 예제의 데이터베이스 값을 가져오거나 수정할 수 있다.

즉, 첫번째 앱에 있는 데이터베이스 dic를 두번째 앱인 CP_USE앱에서 수정할 수 있다는 의미이다.

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

클립보드는 임의의 응용 프로그램끼리 데이터를 교환하는 가장 기본적인 방법이다.

 

이것은 드래그&드롭에 의한 데이터 교환을 위해 범용적인 데이터 포맷을 정의할 필요도 있는데 클립보드 포맷이 그 역할을 맡게 된다.

 

시스템 클립보드는 운영체제가 관리하는 임시적인 자료 저장소이다. 임의의 프로그램이 클립보드를 자유롭게 액세스 할 수 있으므로 프로그램 내부의

 

뷰끼리는 물론이고 응용 프로그램끼리도 약속된 방법으로 데이터를 교환할 수 있다.

 

시스템 클립보드는 다음 4가지 포맷을 지원한다.

 

    1. 텍스트 : 가장 일반적인 교환 대상

    2. URI : 복잡한 형태의 데이터는 CP가 제공하며 데이터의 위치를 가리키는 URI를 클립보드에 저장한다.

    3. 인텐트 : 앱을 실행하는 명령과 관련 데이터를 클립도르를 통해 전달한다. 액티비티나 서비스 등에 대한 바로 가기를 교환할 때 유용하다.

    4. HTML 텍스트 : 단순 문자열이 아닌 서식 있는 문자열을 교환한다.

 

클립보드는 시스템 전역적인 자원이다. 운영체제를 전체를 통틀어 딱 하나밖에 없으며 클립보드에 저장되는 클립 데이터도 하나 뿐이다.

 

클립보드를 통한 데이터 교환은 일회적이고 임시적인 것이라 한 번에 하나의 데이터만 교환할 수 있다.

 

클립 데이터에는 데이터뿐만 아니라 데이터의 성격을 명시하는 설명이 포함되어 있다. 설명에는 데이터의 이름인 레이블과 형태를 지정하는 MIME 타입의 배열이 저장된다.

 

응용프로그램은 MIME타입을 통해 클립보드에 저장된 데이터가 문자열인지 URI인지를 판별한다. 텍스트를 붙여 넣은 프로그램에게 클립보드의 인텐트 정보는 무용지물이다.

 

ClipboardManager

 

위 클래스는 시스템 클립보드를 관리하며 클립보드 입출력을 담당한다. 시스템 서비스이므로 객체를 따로 생성할 필요는 없으며 getSystemService메소드로 언제든지 구할 수 있다.

 

다음 메소드로 클립 데이터가 있는지 조사하고 클립데이터를 읽거나 쓴다.

 

    boolean hasPrimaryClip()

    ClipData getPrimaryClip()

    ClipDescription getPrimaryClipDescription()

    void setPrimaryClip(ClipData clip)

 

hasPrimaryClip 메소드는 클립 데이터가 있는지 조사하며 비어 있으면 false를 리턴한다. 이 메소드가 true를 리턴하면 getPrimaryClip 메소드로 클립데이터를 읽는다.

 

물론 원하는 타입이 맞는지 조사한 후 사용해야 하는데 클립 데이터를 읽어 분석하기 전에 설명을 먼저 읽어 보고 타입을 점검할 수 있다.

 

클립보드에 데이터를 저장할 때는 클립 데이터를 만든 후 setPrimaryClip 메소드로 전달한다.

 

다음 메소드는 클립보드가 변경되는지 조사하는 리스너를 등록 및 제거한다. 클립보드의 데이터가 변경될 때마다 리스너의 onPrimaryClipChanged() 메소드가 호출된다.

 

    void addPrimaryClipChangedListener(ClipboardManager.OnPrimaryClipChangedListener what)

    void removePrimaryClipChangedListener(ClipboardManager.OnPrimaryClipChangedListener what)

 

클립보드가 변경되는 시점에 특정한 시점에 작업을 해야 한다면 이 리스너를 등록하고 핸들러에 코드를 작성한다.

 

ClipData클래스

 

클립보드에 저장되는 데이터를 표현하며 클립보드에 들어가는 내용물이다. 앞에서 알아본 바대로 설명과 데이터의 컬렉션이 저장된다.

 

다음 메소드로 항목의 개수를 조사하거나 항목을 읽고 추가한다.

 

    int getItemCount()

    ClipData.Item getItemAt(int index)

    void addItem(ClipData.Item item)

    CipDescription getDescription()

 

하나의 항목만 저장되는 경우 getItemAt(0) 호출로 항목을 바로 구할 수 있다.

 

클립 데이터를 생성할 때는 저장할 항목의 타입에 따라 다음 정적 메소드를 사용한다.

 

    ClipData newPlainText(CharSequence label, CharSequence text)

    ClipData newRawUri(CharSequence label, Uri uri)

    CilpData newUri(ContentResolver resolver, CharSequence label, Uri uri)

    ClipData newIntent(CharSequence label, Intent intent)

    ClipData newHtmlText(CharSequence label, CharSequence text, String htmlText)

 

데이터에 대한 간략한 설명인 레이블과 실제 데이터를 인수로 전달하면 클립 데이터 객체가 생성된다.

 

이렇게 만든 클립 데이터를 관리자의 setPrimaryClip 메소드로 클립보드에 넣어 복사한다.

 

ClipDescription 클래스

 

클립보드에 저장된 데이터에 대한 설명을 제공한다. 데이터에 대한 짧은 이름인 레이블과 데이터의 형태인 마임 타입이 저장되어 있다.

 

현재 지원하는 타입은 다음과 같다.

 

마임 타입

설명

MIMETYPE_TEXT_PLAIN(text/plain)

평범한 일반 문자열이다.

MIMETYPE_TEXT_INTENT(text/vnd.android.intent)

인텐트이다.

MIMETYPE_TEXT_URILIST(text/uri-list)

URI 목록이다.

MIMETYPE_TEXT_HTML(text/html)

서식있는 HTML 문자열이다.

 

다음 메소드는 레이블, 마임 타입을 조사하거나 특정한 타입이 저장되어 있는지 조사한다.

 

    CharSequence getLabel()

    int getMimeTypeCount()

    String getMimeType(int index)

    boolean hasMimeType(String mimeType)

 

클립보드를 읽기 전에 hasMimeType 메소드로 원하는 포맷이 있는지 조사해야 한다.

 

ClipData.Item 클래스

 

클립보드에 저장되는 데이터 하나이며 응용 프로그램끼리 교환할 실제 데이터이다. 클립보드에 복사할 때는 이 객체를 직접 생성하는 것보다 ClipData의 new* 정적 메소드를 사용하는 것이 일반적이다.

 

복수개의 항목을 저장할 때는 생성자로 객체를 직접 생성한 후 addItem 메소드로 항목을 추가한다. 붙여 넣을 때는 데이터 타입에 따라 다음 메소드 중 하나로 실제 값을 읽는다.

 

    CharSequence getText()

    Uri getUri()

    Intent getIntent()

    String getHtmlText()

 

문자열은 클립보드 자체에 저장되므로 읽어서 바로 사용할 수 있지만 Uri나 Intent는 읽은 후 원하는 데이터를 구하기 위해 추가적인 작업이 필요하다.

 

다음 메소드는 타입에 상관없이 강제로 문자열형태로 바꿔서 읽는다.

 

    CharSequence coerceToText(Context context)

    CharSequence coerceToStyledText(Context context)

    String coerceToHtmlText(Context context)

 

이 메소드는 붙여 넣기보다는 디버깅용이나 클립보드 관리 목적으로 주로 사용된다.

 

다음 예제는 간단한 텍스트를 클립보드에 저장하고 가져와서 붙여넣기를 해본다.

 

다음과 같이 프로젝트를 구성한다.

 

copytext.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

    >

 

    <EditText

     android:id="@+id/copyedit"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:text="Clipboard Test"/>

    <Button

     android:id="@+id/btncopy"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:onClick="mOnClick"

     android:text="Copy"

     />

    <TextView

     android:id="@+id/pastetext"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:text="paste here"

     />

    <Button

     android:id="@+id/btnpaste"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:onClick="mOnClick"

     android:text="Paste"/>

    

</LinearLayout>

 

 

CopyText.java

 

package com.example.ch27_copytext;

 

import android.app.Activity;

import android.content.ClipData;

import android.content.ClipDescription;

import android.content.ClipboardManager;

import android.content.Context;

import android.os.Bundle;

import android.view.View;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.Toast;

 

public class CopyText extends Activity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.copytext);

    }

 

    

    //버튼 클릭시

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btncopy:

            copyText();

            break;

        case R.id.btnpaste:

            pasteText();

            break;

        }

    }

    

    void copyText(){

        EditText copyedit = (EditText)findViewById(R.id.copyedit);

        String text= copyedit.getText().toString();//에디트텍스트에 입력된 가져옴

        

        if(text.length() != 0){

            //문자열을 클립보드에 넣는수 있는 클립데이터 형태로 포장

            ClipData clip = ClipData.newPlainText("text", text);

            

            //클립보드 관리자 객체를 가져옴

            ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

            cm.setPrimaryClip(clip);//클립보드에 저장하는 부분

            Toast.makeText(this, "Text Copied", 0).show();

        }

    }

    

    void pasteText(){

        //클립보드 관리자 객체를 가져옴

        ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

        //클립보드에 값이 없으면

        if(cm.hasPrimaryClip() == false){

            Toast.makeText(this, "clipboard empty", 0).show();

            return;

        }

        

        //클립보드의 값이 텍스트가 아니면?

        if(cm.getPrimaryClipDescription().hasMimeType(

                ClipDescription.MIMETYPE_TEXT_PLAIN)==false){

            Toast.makeText(this, "clip is not text", 0).show();

            return;

        }

                

        //클립데이터를 읽는다.

        ClipData clip = cm.getPrimaryClip();

        ClipData.Item item = clip.getItemAt(0);//첫번째 데이터를 가져옴

        TextView pastetext = (TextView)findViewById(R.id.pastetext);

        pastetext.setText(item.getText());//텍스트뷰에 세팅해줌

        

    }

    

}

 

 

 

실행 화면

에디트 텍스트에 값을 쓰고 Copy버튼을 누르고 Paste버튼을 누르면 다음과 같이 텍스트뷰가 바뀐다.

 

다른 앱에 가서도 붙여넣기를 하면 위에 클립보드에 저장한 값을 붙여넣기 할 수 있다.

 

 

 

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

압축 파일 관리 기능은 자바에 의해 언어 차원에서 제공되므로 안드로이드도 별도의 추가 라이브러리 없이 압축 파일을 만들거나 해제할 수 있다.

 

ZipFile 클래스는 파일 기반의 zip 압축 파일을 다루며 압축 파일 내의 임의 파일을 랜덤으로 액세스한다.

 

다음 두가지 생성자가 있으며 File 객체로부터 생성할 수도 있고 압축 파일의 경로를 주어 열 수도 있다.

    

    ZipFile(File file [, int mode])

    ZipFile(String name)

 

File 객체로 생성할 때 mode에 OPEN_DELETE를 지정하면 사용 후 자동으로 삭제되므로 임시적으로 압축 파일을 만들어 할용할 수 있다.

 

    Enumeration<? extends ZipEntry> entries()

    ZipEntry getEntry(String entryName)

    int size()

    InputStream getInputStream(ZipEntry entry)

 

entries메소드는 압축 파일에 들어간 순서대로 모든 파일과 디렉터리의 목록을 구하며 getEntry 메소드는 지정한 경로의 항목을 구한다.

size는 압축 파일에 포함된 항목의 총 개수를 구한다. getInputStream은 항목을 액세스할 수 있는 스트림을 구하며 이 스트림에서 데이터를 읽음으로써 압축을 푼 데이터를 추출한다.

 

압축파일에 포함된 항목 하나는 ZipEntry클래스로 표현한다. 관련 메소드는 다음과 같다.

 

메소드

설명

String getName()

파일의 이름

long getSize()

압축을 풀었을 때의 원래 크기

long getCompressedSize()

압축된 크기

long getCrc()

체크섬

long getTime()

최후 수정된 시간

boolean isDirectory()

디렉터리인지 조사한다.

 

다음 동그라미 친 부분을 유의하자 assets에 ZipTest.zip 파일 추가해야 예제가 잘 된다.

ZipTest.zip 다운하기

ZipTest.zip

 

 

이번 예제는 SD카드의 정보를 사용하므로 매니페스트 파일에 퍼미션을 추가해야한다.

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch25_readzip"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".ReadZip"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

 

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

</intent-filter>

</activity>

</application>

 

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

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

 

</manifest>

 

readzip.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

 

<Button

android:id="@+id/btnlist"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="mOnClick"

android:text="list"

/>

<Button

android:id="@+id/btna"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="mOnClick"

android:text="a.txt"

/>

<Button

android:id="@+id/btnb"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="mOnClick"

android:text="b.txt"

/>

    <TextView

     android:id="@+id/result"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:text="result"

     />

 

</LinearLayout>

 

ReadZip.java

package com.example.ch25_readzip;

 

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.Enumeration;

import java.util.zip.ZipEntry;

import java.util.zip.ZipFile;

import java.util.zip.ZipInputStream;

 

import android.app.Activity;

import android.content.Context;

import android.content.res.AssetManager;

import android.os.Bundle;

import android.os.Environment;

import android.view.View;

import android.widget.TextView;

 

public class ReadZip extends Activity {

    TextView mResult;

    String mPath;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.readzip);

      

        mPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ZipTest.zip";

        mResult = (TextView)findViewById(R.id.result);

        

        //에셋의 zip 파일 복사하기

        CopyAsset(this, "ZipTest.zip", "ZipTest.zip");

    }

 

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btnlist:

            ShowList();

            break;

        case R.id.btna:

            ShowA();

            break;

        case R.id.btnb:

            ShowB();

            break;

        }

    }

    

    //Zip파일을 읽은 다음 정보를 보여주는 메소드

    void ShowList(){

        try{

            ZipFile zip = new ZipFile(mPath);

            String s = "";

            s = "size = " + zip.size() + "\n";

            ZipEntry e;

            

            Enumeration<? extends ZipEntry> ent = zip.entries();//zip파일의 엔트리를 가져옴

            while(ent.hasMoreElements()){//엔트리가 있으면

                e = (ZipEntry)ent.nextElement();//가져옴

                s = s + "name = " + e.getName() + " , size = " + e.getSize() +

                        " , Compsize = " + e.getCompressedSize() + "\n";

            }

            

            mResult.setText(s);

        }catch(Exception e){

            return;

        }

    }

    

    void ShowA(){

        try{

            ZipFile zip;

            zip = new ZipFile(mPath);

            

            //a.txt 읽기 위해

            InputStream is = zip.getInputStream(zip.getEntry("a.txt"));

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            byte[] buffer = new byte[1024];

            

            int len;

            //내용을 출력하는 부분

            for(;;){

                len = is.read(buffer);

                if(len <= 0)break;

                baos.write(buffer, 0, len);

            }

            is.close();

            mResult.setText(baos.toString());

        }catch(IOException e){

            

        }

    }

 

    void ShowB(){

        try{

            ZipInputStream zin = new ZipInputStream(new FileInputStream(mPath));

            for(;;){

                ZipEntry ze = zin.getNextEntry();//다음폴더를 들어가기위해

                if(ze == null) break;

                if(ze.getName().equals("subdir/b.txt")){//찾으면?

                    //아래부분은 출력을 하는부분

                    ByteArrayOutputStream baos = new ByteArrayOutputStream();

                    byte[] buffer = new byte[1024];

                    

                    int len;

                    for(;;){

                        len = zin.read(buffer);

                        if(len <= 0) break;

                        baos.write(buffer, 0, len);

                    }

                    mResult.setText(baos.toString());

                    break;

                }

            }

            zin.close();

        }catch(Exception e){

            

        }

    }    

    

    //Zip파일을 sd카드에 복사하는 메소드

    public boolean CopyAsset(Context context, String src, String dest){

        if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){

            return false;

        }

        

        String root = Environment.getExternalStorageDirectory().getAbsolutePath();

        

        String destpath = root + "/" + dest;

        File f = new File(destpath);

        if(f.exists()){

            return true;

        }

        

        AssetManager am = context.getAssets();

        //파일을 실제로 복사하는 부분

        try{

            InputStream is = am.open(src);

            FileOutputStream os = new FileOutputStream(destpath);

            byte buffer[] = new byte[1024];

            for(;;){

                int read = is.read(buffer);

                if(read <= 0) break;//파일 읽음

                os.write(buffer, 0, read);

            }

            is.close();

            os.close();

        

        }catch(IOException e){

            return false;

        }

 

        

        return true;

    }

    

}

 

 

출력 화면

 

 

a.txt 버튼을 눌렀을 때

 

b.txt 버튼을 눌렀을 때

 

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

파일 입출력 메소드는 파일 안에 저장된 데이터를 관리하는데 비해 파일 관리 메소드는 파일 그 자체가 관리 대상이다.

 

File 클래스의 다음 메소드는 파일의 목록을 구한다.

 

    String[] list([FilenameFilter filter])

    File[] listFiles([FilenameFilter filter])

 

파일의 경로 목록을 구할 수도 있고 File 객체의 목록을 구할 수도 있다.

 

filter를 지정하면 특정 조건에 맞는 파일의 목록만 조사하며 생략하면 모든 파일이 조사된다. 디렉터리도 같이 조사되지만 현재 디렉터리인 .과 부모 디렉터리인 ..은 제외된다.

만약 파일이 하나도 없으면 null이 리턴된다.

 

다음 메소드는 파일의 이름을 변경하거나 삭제한다.

 

    boolean renameTo(File newPath)

    boolean delete()

    void deleteOnExit()

 

둘 다 패키지 디렉터리 아래의 파일만 관리할 수 있으며 경로는 역시 사용할 수 없다. deleteOnExit는 가상 머신이 종료될 때 삭제하도록 예약하는 것이다.

 

다음 메소드는 파일에 대한 정보를 조사한다.

 

    boolean exists()

    boolean isFile()

    boolean isDirectory()    

    long length()

    boolean isHidden()

    long lastModified()

    boolean canRead()

    boolean canWrite()

 

디렉터리를 생성할 때는 다음 메소드를 사용한다.

    boolean mkdir()

    boolean mkdirs()

 

mkdir은 부모 폴더까지 생성하지 않는데 비해 mkdirs는 부모 폴더까지 한꺼번에 생성한다는 점이 다르다.

 

다음 예제는 SD카드의 파일과 디렉터리 목록을 보여준다.

 

 

SD카드를 사용하기 때문에 반드시 매니페스트 파일에 퍼미션을 추가해야한다.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch25_fileexplorer"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".FileExplorer"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

 

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

</intent-filter>

</activity>

</application>

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

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

</manifest>

 

 

fileexplorer.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

 

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical"

>

 

<TextView

android:id="@+id/current"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="current"

/>

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content">

<Button

android:id="@+id/btnroot"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="mOnClick"

android:text="Root"/>

<Button

android:id="@+id/btnup"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="mOnClick"

android:text="Up"/>

 

</LinearLayout>

</LinearLayout>

    <ListView

     android:id="@+id/filelist"

     android:layout_width="match_parent"

     android:layout_height="match_parent"

     />

 

</LinearLayout>

 

 

FileExplorer.java

 

package com.example.ch25_fileexplorer;

 

import java.io.File;

import java.util.ArrayList;

 

import android.app.Activity;

import android.os.Bundle;

import android.os.Environment;

import android.view.View;

import android.widget.AdapterView;

import android.widget.ArrayAdapter;

import android.widget.ListView;

import android.widget.TextView;

import android.widget.Toast;

 

 

public class FileExplorer extends Activity {

    String mCurrent;

    String mRoot;

    TextView mCurrentTxt;

    ListView mFileList;

    ArrayAdapter<String> mAdapter;

    ArrayList<String> arFiles;

      

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.fileexplorer);

      

        mCurrentTxt = (TextView)findViewById(R.id.current);

        mFileList = (ListView)findViewById(R.id.filelist);

        

        arFiles = new ArrayList<String>();

        //SD카드 루트 가져옴

        mRoot = Environment.getExternalStorageDirectory().getAbsolutePath();

        mCurrent = mRoot;

        

        //어댑터를 생성하고 연결해줌

        mAdapter = new ArrayAdapter<String>(this,

                android.R.layout.simple_list_item_1, arFiles);

        mFileList.setAdapter(mAdapter);//리스트뷰에 어댑터 연결

        mFileList.setOnItemClickListener(mItemClickListener);//리스너 연결

        

        refreshFiles();

        

    }

    

    //리스트뷰 클릭 리스너

    AdapterView.OnItemClickListener mItemClickListener =

            new AdapterView.OnItemClickListener() {

 

                @Override

                public void onItemClick(AdapterView<?> parent, View view,

                        int position, long id) {

                    // TODO Auto-generated method stub

                    String Name = arFiles.get(position);//클릭된 위치의 값을 가져옴

                    

                    //디렉토리이면

                    if(Name.startsWith("[") && Name.endsWith("]")){

                        Name = Name.substring(1, Name.length() - 1);//[]부분을 제거해줌

                    }

                    //들어가기 위해 / 터치한 파일 명을 붙여줌

                    String Path = mCurrent + "/" + Name;

                    File f = new File(Path);//File 클래스 생성

                    if(f.isDirectory()){//디렉토리면?

                        mCurrent = Path;//현재를 Path 바꿔줌

                        refreshFiles();//리프레쉬

                    }else{//디렉토리가 아니면 토스트 메세지를 뿌림

                    Toast.makeText(FileExplorer.this, arFiles.get(position), 0).show();

                }

            }

    };

    

    //버튼 2 클릭시

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btnroot://루트로 가기

            if(mCurrent.compareTo(mRoot) != 0){//루트가 아니면 루트로 가기

                mCurrent = mRoot;

                refreshFiles();//리프레쉬

            }

            break;

        case R.id.btnup:

            if(mCurrent.compareTo(mRoot) != 0){//루트가 아니면

                int end = mCurrent.lastIndexOf("/");/// 나오는 마지막 인덱스를 찾고

                String uppath = mCurrent.substring(0, end);//그부분을 짤라버림 위로가게됨

                mCurrent = uppath;

                refreshFiles();//리프레쉬

            }

            break;

        }

    }

      

    

    void refreshFiles(){

        mCurrentTxt.setText(mCurrent);//현재 PATH 가져옴

        arFiles.clear();//배열리스트를 지움

        File current = new File(mCurrent);//현재 경로로 File클래스를 만듬

        String[] files = current.list();//현재 경로의 파일과 폴더 이름을 문자열 배열로 리턴

        

        //파일이 있다면?

        if(files != null){

            //여기서 출력을 해줌

            for(int i = 0; i < files.length;i++){

                String Path = mCurrent + "/" + files[i];

                String Name = "";

                

                File f = new File(Path);

                if(f.isDirectory()){

                    Name = "[" + files[i] + "]";//디렉토리면 [] 붙여주고

                }else{

                    Name = files[i];//파일이면 그냥 출력

                }

                

                arFiles.add(Name);//배열리스트에 추가해줌

            }

        }

        //다끝나면 리스트뷰를 갱신시킴

        mAdapter.notifyDataSetChanged();

    }

}

 

 

출력 화면

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

안드로이드 3.0 부터 하드웨어 가속 기능을 지원한다. 하드웨어 가속은 그래픽 처리를 할 때 GPU를 사용하여 그리는 방식이다.

 

하지만 다음 메소드는 아직 가속을 지원하지 않는다.

 

클래스

메소드

Canvas

clipPath, clipRegion, drawPicture, drawTextOnPath, drawVertices

Paint

setLinearText, setMaskFilter, setRasterizer

Xfermodes

AvoidXfermode, PixelXorXfermode

 

다음 기능은 하드웨어 가속을 사용하면 다르게 동작하는 메소드이다.

 

클래스

메소드

Canvas

clipRect - 일부 모드가 무시된다.

drawBitmapMesh - 색상 배열이 무시된다.

Paint

setDither - 무시된다.

setFilterBitmap - 필터링이 항상 켜진다.

setShadowLayer - 텍스트에 대해서만 정상 작동한다.

PorterDuffXfermode

DARKEN, LIGHTEN, OVERLAY 모드가 SRC_OVER와 같음

ComposeShader

ComposeShader가 같은 타입의 셰이더는 조합하지 못하며 중첩도 안된다.

 

 

하드웨어 가속 기능은 속도를 향상시키는 것이 주목적이며 출력의 결과나 품질과는 상관 없다.

 

개발자는 앱, 액티비티, 윈도우, 뷰 4가지 수준에서 가속 기능의 사용 여부를 선택할 수 있다.

 

앱과 액티비티의 가속 기능 사용 여부는 매니페스트의 속성으로 제어한다.

 

<application android:hardwareAccelerated="true">

    <activity name="A"/>

    <activity name="B"/>

    <activity name="C" android:hardwareAccelerated="false"/>

</application>

 

위와 같이 hardwareAccelerated 속성을 지정함으로써 가속 기능의 사용 여부를 나타낸다.

 

윈도우는 setFlags 메소드로 FLAG_HARDWARE_ACCELERATED 플래그를 지정함으로써 가속 기능을 선택한다.

 

하지만 액티비티의 가속 설정을 조정하는 것이 더 편리해서 이 플래그를 쓸일은 없다.

 

액티비티는 가속을 쓰지만 뷰만 쓰고 싶지 않다면 다음 메소드를 호출하면 된다.

 

    myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

 

현재 가속 기능이 적용되고 있는지 아닌지는 다음 2가지 방법으로 조사한다. (뷰나 캔버스에 대해서)

 

    boolean View.isHardwareAccelerated()

    boolean Canvas.isHardwareAccelerated()

 

뷰는 가속 중이라도 비트맵에 그릴 때 캔버스는 가속상태가 아닐 수도 있으므로 뷰보다는 캔버스의 가속 상태를 조사하는 것이 더 정확하다.

(캔버스에 직접 그리기를 하지 않는다면 뷰를 통해 조사하면됨)

 

이번 예제는 하드웨어 가속을 사용할 때와 사용하지 않았을 때의 비교이다.

아래와 같이 2개의 파일만 수정한다.

 

AccelTest1.java

 

package com.example.ch22_acceltest1;

 

import java.util.Random;

 

import android.app.Activity;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.os.Bundle;

import android.view.View;

 

public class AccelTest1 extends Activity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //setContentView(R.layout.acceltest1);

        AccelTestView vw = new AccelTestView(this);

        setContentView(vw);

    }

 

}

 

class AccelTestView extends View{

 

    Random Rnd = new Random();

    int Radius = 1;

    public AccelTestView(Context context){

        super(context);

    }

    

    public void onDraw(Canvas canvas){

        canvas.drawColor(Color.GRAY);

        Paint Pnt = new Paint();

        Pnt.setAntiAlias(true);

        Pnt.setTextSize(30);

        

        if(canvas.isHardwareAccelerated()){

            canvas.drawText("Hardware Accel", 50, 50, Pnt);

        }else{

            canvas.drawText("Software Draw", 50, 50, Pnt);

        }

        

        Pnt.setStyle(Paint.Style.STROKE);

        Pnt.setStrokeWidth(3);

        Radius += 3;

        if(Radius > 255) Radius = 1;

        for(int i = 1; i < Radius; i+=3){

            Pnt.setColor(Color.rgb(i, i, i));

            canvas.drawCircle(240, 400, i, Pnt);

        }

        invalidate();

    }

}

 

AndroidMenifest,xml 파일

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.ch22_acceltest1"

android:versionCode="1"

android:versionName="1.0" >

 

<uses-sdk

android:minSdkVersion="19"

android:targetSdkVersion="19" />

 

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".AccelTest1"

android:label="@string/app_name"

 

android:hardwareAccelerated="true">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

 

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

</intent-filter>

</activity>

</application>

 

</manifest>

 

실행 화면

 

    하드웨어 가속을 사용하지 않았을 때는 동그라미를 그릴 때 약간 부드럽지가 않다.

메니페스트 파일에서 hardwareAccelerated를 false로 둔다.

 

 

하드웨어 가속을 사용하면 끊김 없이 부드럽게 동그라미가 그려진다.

메니페스트 파일에서 hardwareaccelerated 를 true로 둔다.

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

액션 모드는 특정 상황에 임시적으로 열리는 액션바다. (에디트 텍스트를 길게 누른다던지.._

 

액션 모드는 이전에 컨텍스트 메뉴를 대체하는 장치이다.

 

액션 모드는 액티비티의 다음 메소드로 시작한다.

 

    ActionMode startActionMode(ActionMode.Callback callback)

 

인수로 액션 모드의 메뉴를 관리하는 콜백 객체를 요구하며 생성된 액션 모드 객체를 리턴한다. 콜백 인터페이스의 메소드는 옵션 메뉴의 콜백과 유사하다.

 

    boolean onCreateActionMode(ActionMode mode, Menu menu)

    boolean onPrepareActionMode(ActionMode mode, Menu menu)

    boolean onActionItemClicked(ActionMode mode, MenuItem item)

    void onDestroyActionMode(ActionMode mode)

 

메뉴의 생성, 준비, 선택, 파괴 시에 각 콜백이 호출된다. 액티비티에 정의된 메뉴 관련 콜백과 거의 비슷하지만 인수로 ActionMode를 받는다는 점이 다르다.

 

다음은 버튼을 누르면 액션 모드를 보여주는 예제이다.

 

예제 구성

 

 

actionmodetestmenu.xml

 

<menu xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

tools:context="com.example.ch21_actionmodetest.ActionModeTest" >

 

<item

android:id="@+id/add"

android:title="add"

android:icon="@android:drawable/ic_menu_add"

android:showAsAction="always|withText"

/>

<item

android:id="@+id/edit"

android:title="edit"

android:icon="@android:drawable/ic_menu_edit"

android:showAsAction="always|withText"

/>

<item

android:id="@+id/search"

android:title="search"

android:icon="@android:drawable/ic_menu_search"

android:showAsAction="always|withText"

/>

 

</menu>

 

 

actionmodetest.xml

 

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

>

 

<Button

android:id="@+id/btnaction"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="mOnClick"

android:text="Action Mode"

/>

 

</LinearLayout>

 

 

ActionModeTest.java

 

package com.example.ch21_actionmodetest;

 

import android.app.Activity;

import android.os.Bundle;

import android.view.ActionMode;

import android.view.Menu;

import android.view.MenuInflater;

import android.view.MenuItem;

import android.view.View;

import android.widget.Toast;

 

public class ActionModeTest extends Activity {

    ActionMode mActionMode;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.actionmodetest);

    }

 

    public void mOnClick(View v){

        switch(v.getId()){

        case R.id.btnaction:

            //버튼 클릭시 액션 모드가 실행된다.

            if(mActionMode == null){

                mActionMode = startActionMode(mActionCallback);

                mActionMode.setTitle("Test");

            }

            break;

        }

    }

    

    ActionMode.Callback mActionCallback = new ActionMode.Callback() {

        

        @Override

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {

            // TODO Auto-generated method stub

            return false;

        }

        

        @Override

        public void onDestroyActionMode(ActionMode mode) {

            // TODO Auto-generated method stub

            //액션 모드가 끝나면 mActionMode 해지한다.

            mActionMode = null;

        }

        

        @Override

        public boolean onCreateActionMode(ActionMode mode, Menu menu) {

            // TODO Auto-generated method stub

            //액션 모드 메뉴를 생성해주는 부분

            MenuInflater inflater = mode.getMenuInflater();

            inflater.inflate(R.menu.actionmodetestmenu, menu);

            

            return true;

        }

        

        @Override

        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {

            // TODO Auto-generated method stub

            //액션 모드의 아이템이 클릭됬을때 처리는 여기서

            switch(item.getItemId()){

            case R.id.add:

                Toast.makeText(ActionModeTest.this, "add", 0).show();

                break;

            case R.id.edit:

                Toast.makeText(ActionModeTest.this, "edit", 0).show();

                break;

            case R.id.search:

                Toast.makeText(ActionModeTest.this, "search", 0).show();

                break;

            }

            

            return false;

        }

    };

    

}

 

 

실행 화면

 

 

버튼을 누르면 다음과 같이 뜬다

 

연필 모양 클릭시 토스트 메시지가 나온다.

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

다음 메소드를 활용하여 액션바의 출력 옵션을 변경하거나 꾸밀 수 있다.

 

    void setDisplayOptions(int options [, int mask])

    int getDisplayOptions()

 

지원 가능한 옵션의 종류는 다음과 같다.

 

옵션

설명

DISPALY_USE_LOGO

0x01

앱아이콘 대신에 로고를 표시한다.

DISPLAY_SHOW_HOME

0x02

홈 요소를 보여준다.

DISPLAY_HOME_AS_UP

0x04

왼쪽 화살표 모양의 Up버튼을 보여준다.

DISPLAY_SHOW_TITLE

0x08

타이틀과 서브 타이틀을 보여준다.

DISPLAY_SHOW_CUSTOM

0x10

커스텀 뷰를 보여준다.

 

또는 다음의 옵션을 개별적으로 지정하는 메소드를 호출해도 결과는 동일하다.

 

    void setDisplayUseLogoEnabled(boolean useLogo)

    void setDisplayShowHomeEnabled(boolean showHome)

    void setDisplayHomeAsUpEnabled(boolean showTitle)

    void setDisplayShowCustomEnabled(boolean showCustom)

 

액티비티의 로고 아이콘은 매니페스트에 logo 속성으로 지정한다.

 

    <activity android:name="패키지명.클래스명"

        android:label="DisplayOption"

        android:logo="@drawable/androboy"/>

 

또는 다음 메소드로 로고를 변경 할 수 있다.

    

    void setLogo(int resId)

    void setLogo(Drawable logo)

 

커스텀 뷰는 액션바에 별도의 뷰를 추가로 하나 더 배치하는 것이며 로고 아이콘과 액션 항목 사이에 나타난다.

 

다음 메소드로 커스텀 뷰를 배치한다.

 

    void setCustomView(int resId)

    void setCustomView(View view [, ActionBar.LayoutParams layoutParams])

    View getCustomView()

 

다음 메소드로 서브 타이틀을 하나 더 붙일 수 있다.

 

    void setSubtitle(int resId)

    void setSubtitle(CharSequence subtitle)

 

다음 예제는 실제 옵션 값을 체크 해본다.

 

아래와 같이 파일을 구성한다.

 

displayoption.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

<CheckBox

    android:id="@+id/chkuselogo"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="USE LOGO" />

<CheckBox

    android:id="@+id/chkshowhome"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="SHOW HOME" />

<CheckBox

    android:id="@+id/chkhomeasup"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="HOME AS UP" />

<CheckBox

    android:id="@+id/chkshowtitle"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="SHOW TITLE" />

<CheckBox

    android:id="@+id/chkshowcustom"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="SHOW CUSTOM" />

</LinearLayout>

 

actionbarmenu.xml

 

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:title="하나"

        android:icon="@android:drawable/ic_menu_add"

        android:showAsAction="always|withText"

        />

    <item android:title=""

        android:icon="@android:drawable/ic_menu_edit"

        android:showAsAction="ifRoom"

        />

    <item android:title=""

        android:showAsAction="ifRoom"

        />

    <item android:title=""

        android:showAsAction="ifRoom"

        />

    <item android:title="다섯"

        android:showAsAction="ifRoom"

        />

    <item android:title="여섯" />

</menu>

 

DisplayOption.java

 

package com.example.ch21_displayoption;

 

import android.app.ActionBar;

import android.app.Activity;

import android.os.Bundle;

import android.view.Menu;

import android.view.MenuInflater;

import android.widget.Button;

import android.widget.CheckBox;

import android.widget.CompoundButton;

 

 

public class DisplayOption extends Activity {

    CheckBox mChkUseLogo;

    CheckBox mChkShowHome;

    CheckBox mChkHomeAsUp;

    CheckBox mChkShowTitle;

    CheckBox mChkShowCustom;

    ActionBar mActionBar;

    Button mCustom;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.displayoption);

      

        mChkUseLogo = (CheckBox)findViewById(R.id.chkuselogo);

        mChkUseLogo.setOnCheckedChangeListener(mListener);

        mChkShowHome = (CheckBox)findViewById(R.id.chkshowhome);

        mChkShowHome.setOnCheckedChangeListener(mListener);

        mChkHomeAsUp = (CheckBox)findViewById(R.id.chkhomeasup);

        mChkHomeAsUp.setOnCheckedChangeListener(mListener);

        mChkShowTitle = (CheckBox)findViewById(R.id.chkshowtitle);

        mChkShowTitle.setOnCheckedChangeListener(mListener);

        mChkShowCustom = (CheckBox)findViewById(R.id.chkshowcustom);

        mChkShowCustom.setOnCheckedChangeListener(mListener);

        

        mActionBar = getActionBar();

        mCustom = new Button(this);

        mCustom.setText("Custom");

        mActionBar.setCustomView(mCustom);

        

        mActionBar.setSubtitle("subtitle");

        

        int option = mActionBar.getDisplayOptions();

        //개별 옵션 상태를 조사하기위해 다음과같이 비트연산을 해야한다.

        mChkUseLogo.setChecked((option & ActionBar.DISPLAY_USE_LOGO) != 0);

        mChkShowHome.setChecked((option & ActionBar.DISPLAY_SHOW_HOME) != 0);

        mChkHomeAsUp.setChecked((option & ActionBar.DISPLAY_HOME_AS_UP) != 0);

        mChkShowTitle.setChecked((option & ActionBar.DISPLAY_SHOW_TITLE) != 0);

        mChkShowCustom.setChecked((option & ActionBar.DISPLAY_SHOW_CUSTOM) != 0);

        

    }

 

    public boolean onCreateOptionsMenu(Menu menu){

        super.onCreateOptionsMenu(menu);

        MenuInflater inflater = getMenuInflater();

        inflater.inflate(R.menu.actionbarmenu, menu);

      

        return true;

    }

 

    //체크박스 리스너

    CheckBox.OnCheckedChangeListener mListener =

            new CheckBox.OnCheckedChangeListener(){

 

                @Override

                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                    // TODO Auto-generated method stub

                    switch(buttonView.getId()){

                    case R.id.chkuselogo:

                        mActionBar.setDisplayUseLogoEnabled(isChecked);

                        break;

                    case R.id.chkshowhome:

                        mActionBar.setDisplayShowHomeEnabled(isChecked);

                        break;

                    case R.id.chkhomeasup:

                        mActionBar.setDisplayHomeAsUpEnabled(isChecked);

                        break;

                    case R.id.chkshowtitle:

                        mActionBar.setDisplayShowTitleEnabled(isChecked);

                        break;

                    case R.id.chkshowcustom:

                        mActionBar.setDisplayShowCustomEnabled(isChecked);

                        break;                        

                    }

                }

            

    };

    

}

 

 

실행 화면

 

 

블로그 이미지

가카리

소프트웨어와 하드웨어 프로그래밍, 취업 및 직장생활 전문 블로그

 

일반 대화상자에 비해 대화상자 프래그먼트는 다양한 스타일과 테마를 쉽게 적용할 수 있다는 이점이 있다.

 

프래그먼트는 다음 메소드만 호출하면 된다. 단 스타일과 테마는 대화상자를 생성할 때 적용 되므로 생성하기 전에 이 메소드를 호출해야한다.

 

    void setStyle(int style, int theme)

 

다음 예제는 스타일과 테마의 조합 몇 가지를 보여준다.

 

그리고 예제는 3개의 파일로 구성된다.

 

dialog_style_theme.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    >

<TextView

    android:id="@+id/result"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:textSize="16sp"

    android:text=""

    />

<Button

    android:id="@+id/btn1"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:onClick="mOnClick"