ListView의 개선형으로 ViewHolder를 포함하고 있고 ListView보다 더 유연하다.

1. build.gradle(module)에 아래 패키지를 포함시킨다.

dependencies { 	... 	implementation "androidx.recyclerview:recyclerview:1.1.0" }

 

2. ListView와 마찬가지로, 표현하려는 객체List를 준비하고, view xml에는 <androidx.recyclerview.widget.RecyclerView> 컴포넌트를 생성하고 id를 부여한다.

<androidx.recyclerview.widget.RecyclerView         android:id="@+id/recyclerView"         android:layout_width="match_parent"         android:layout_height="match_parent"         > </androidx.recyclerview.widget.RecyclerView>

 

3. RecyclerView.Adapter를 상속하는 Adapter를 구현한다.

Adapter 안에는 inner class로 Holder를 포함해서 구현한다.

Holder는 표현할 객체의 속성을 갖도록 한다.

 

class RecyclerViewAdapter(     val itemList: ArrayList<CarForList>     ,val inflater: LayoutInflater ): RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>(){  		// 내부에 Holder 클래스를 포함한다. 		// Holder는 RecyclerView.ViewHolder 클래스를 상속한다. 		// 인자로는 view를 전달 받는다.     inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {         val carName: TextView         val carEngine: TextView          init {             carName = itemView.findViewById(R.id.car_name)             carEngine = itemView.findViewById(R.id.car_engine)              itemView.setOnClickListener {                 //Adapter 안에 inner class에서 adapterPosition 필드에 접근이 가능하다.                 val position = adapterPosition                 //inner 클래스로 선언된 경우에만 outer 클래스의 필드에 접근이 가능해진다.                 val carName = itemList.get(position).name                 val carEngine = itemList.get(position).engine             }         }     }      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {         val view = inflater.inflate(R.layout.item_view, parent, false)         return ViewHolder(view)     }      override fun getItemCount(): Int {         return itemList.size     }      override fun onBindViewHolder(holder: ViewHolder, position: Int) {         holder.carName.setText(itemList.get(position).name)         holder.carEngine.setText(itemList.get(position).engine)     } }

 

4. 객체 List를 전달해서 adapter 인스턴스를 생성하고, <RecyclerView> 컴포넌트에 주입한다.

val adapter = RecyclerViewAdapter(carList, LayoutInflater.from(this@RecyclerViewActivity)) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.adapter = adapter

 

5. recyclerView 객체에 필요한(Linear, Grid 등) LayoutManager를 삽입하면 view가 생성된다.

recyclerView.layoutManager = LinearLayoutManager(this@RecyclerViewActivity) //recyclerView.layoutManager = GridLayoutManager(this@RecyclerViewActivity, 2)

 

'Android > Android기본' 카테고리의 다른 글

AddView  (0) 2021.11.24
ListView  (0) 2021.11.24
TabLayout, Pager  (0) 2021.11.24
SharedPreference  (0) 2021.11.24
Realm  (0) 2021.11.24

Pager

화면을 밀어서 페이지 넘기듯 넘길 수 있도록 해준다.

각 페이지는 fragment로 구성되는데, Pager안에서 드로우 이벤트에 따라 fragment가 보여지도록 동작한다.

 

 

TabLayout

상/하단 tab을 담당한다. 각 탭을 Pager가 담고있는 fragment와 연결한다.

연결은 두 가지 경우를 각각 해주어야 한다.

  1. Pager 조작에 따라 tab이 선택되도록
  1. tab 선택에 따라 Pager가 보여주는 fragment가 선택되도록

 

 

 

Adapter

보통 Page와 Tab을 연결해서 tab을 클릭하면 지정된 page로 이동하도록 한다.

이를 연결해주는 역할이 adapter

 

 

  1. TabLayout을 사용하기 위해 build.gradle(module)에 아래 패키지를 추가한다.
    dependencies { 	... 	implementation 'com.google.android.material:material:1.1.0' }
  1. view.xml에 tab이 되는 <com.google.android.material.tabs.TabLayout>와Pager가 되는 <androidx.viewpager.widget.ViewPager> 컴포넌트를 배치한다.
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     tools:context=".TabPager2Activity"     android:orientation="vertical">      <com.google.android.material.tabs.TabLayout         android:id="@+id/tab_layout2"         android:layout_width="match_parent"         android:layout_height="wrap_content"         />      <androidx.viewpager.widget.ViewPager         android:id="@+id/view_pager"         android:layout_width="match_parent"         android:layout_height="match_parent"         />  </LinearLayout>
  2. (tab, pager의 속성과 이를 담는 상위 컴포넌트 Layout의 속성을 주의한다.)
  1. 코드 상에서 TabLayout에 원하는 tab들을 배치하고, pager 객체를 변수에 할당한다.
    val tabLayout = findViewById<TabLayout>(R.id.tab_layout2) 	tabLayout.addTab(tabLayout.newTab().setText("one")) 	tabLayout.addTab(tabLayout.newTab().setText("two")) 	tabLayout.addTab(tabLayout.newTab().setText("three"))  val viewPager = findViewById<ViewPager>(R.id.view_pager)
  1. PagerAdapter를 구현한다.
    class ThreePageAdapter(     val layoutInflater: LayoutInflater ): PagerAdapter() {  	// 선택되는 position에 따라 fragment가 선택되도록 한다. 	// 원하는 fragment는 각각 구현해놓는다.     override fun instantiateItem(container: ViewGroup, position: Int): Any {         when(position) {             0 -> {                 val view = layoutInflater.inflate(R.layout.fragment1, container, false)                 container.addView(view)                 return view             }             1 -> {                 val view = layoutInflater.inflate(R.layout.fragment2, container, false)                 container.addView(view)                 return view             }             2 -> {                 val view = layoutInflater.inflate(R.layout.fragment3, container, false)                 container.addView(view)                 return view             }             else -> {                 val view = layoutInflater.inflate(R.layout.fragment3, container, false)                 container.addView(view)                 return view             }          }     }      override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {         container.removeView(`object` as View)     }      override fun isViewFromObject(view: View, `object`: Any): Boolean {         return view === `object` as View     }      override fun getCount(): Int {         return 3     } }
  1. PagerAdapter 인스턴스를 생성한다.Pager 조작에 따라 tab이 선택되도록 하기위해 viewPager 컴포넌트에 생성한 PagerAdapter 인스턴스를 삽입하고 viewPager에 TabLayoutOnPageChangeListener 를 삽입한다.
  2. // 인스턴스 생성 val pagerAdapter = ThreePageAdapter(LayoutInflater.from(this@TabPager2Activity)) // 인스턴스 주입 viewPager.adapter = pagerAdapter // 리스너 주입 viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
  1. tab 선택에 따라 Pager가 조작되기 위해 tabLayout 컴포넌트에 addOnTabSelectedListener() 메서드로 리스너를 삽입한다.
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{             override fun onTabReselected(tab: TabLayout.Tab?) {              }              override fun onTabUnselected(tab: TabLayout.Tab?) {              }              override fun onTabSelected(tab: TabLayout.Tab?) {                 viewPager.setCurrentItem(tab!!.position)             }         })

 

'Android > Android기본' 카테고리의 다른 글

ListView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24
SharedPreference  (0) 2021.11.24
Realm  (0) 2021.11.24
Async  (0) 2021.04.13

사용자의 동작이나 기호 등 간단한 정보를 저장하기 위해 안드로이드에서 지원하는 DB.

Devide내에 저장되고, 쉽게 사용하고 삭제할 수 있다.

구성은 Key-Value 방식으로 되어있다.

App을 제거하거나 기기 설정에서 해당 app 데이터를 삭제하면 같이 삭제된다.

 

안드로이드 시스템에서 자동으로 생성하거나 반환해주며 editor 객체를 이용해서 데이터를 넣거나 수정, 삭제 할 수 있다.

 

  1. 코드 상에서 getSharedPreferences(<sharedPreference 이름>, <Mode>) 메서드로 sharedPareference 객체를 받는다.
    val sharedPreference = getSharedPreferences("sp1", Context.MODE_PRIVATE)
    <sharedPreference 이름> 지정한 이름으로된 DB가 device에 없다면 새롭게 생성되서 반환된다.
    // Mode         // MODE_PRIVATE : 생성한 application에서만 사용 가능하다.         // MODE_WORLD_READABLE : 다른 app에선 읽기만 가능         // MODE_WORLD_WRITABLE : 다른 app에서 읽기, 쓰기 모두 가능         // MODE_MULTI_PROCESS : 이미 호출되 사용중인지 체크         // MODE_APPEND : 기존 preference에 추가
  2. 인자로 넘겨주는 Mode는 다음과 같다.
  1. SharedPreference DB인스턴스는 editor 객체로 데이터를 넣거나 조작한다.
    val editor: SharedPreferences.Editor = sharedPreference.edit()  // insert editor.putString(<key: String>, <value>)  // delete editor.remove(<key: String>)  // delete all editor.clear()  // commit editor.commit()
  1. 데이터를 꺼내올때는 SharedPreference 인스턴스에서 바로 꺼낸다.
    val sp1 = getSharedPreferences(<sharedPreference name>, <Mode>) sp.getString(<key: String>)

'Android > Android기본' 카테고리의 다른 글

ListView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24
TabLayout, Pager  (0) 2021.11.24
Realm  (0) 2021.11.24
Async  (0) 2021.04.13

RDB를 지원하는 라이브러리

Table 대신 class를 생성하고 attribute에 대응하는 field를 포함시킨다.

field는 필요시 Nullable로 선언한다.

 

Realm 설치 및 초기화

  1. Gradle에 패키지를 포함시킨다.
    // build.gradle(Project) buildscript {     repositories {         jcenter()     }     dependencies {         classpath "io.realm:realm-gradle-plugin:10.0.1"     } }  // build.gradle(module) apply plugin: 'com.android.application' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' // android studio 버전에 따라 아래와 같이 선언하기도 한다. plugins {     id 'com.android.application'     id 'kotlin-android'     id 'kotlin-android-extensions'     id 'kotlin-kapt'     id 'realm-android' }
  1. Table 역할을 할 class를 생성한다.
    package com.example.myapplication  import io.realm.RealmObject  open class School: RealmObject() {     var name: String? = null     var location: String? = null }
  1. Realm을 초기화 시킨다.conifg를 생성해서 Realm을 추가시킨다.
    // Realm 초기화 Realm.init(this@RealmActivity)  // configuration 생성. // deleteRealmIfMigrationNeede() // 기존 Realm에 Attribute가 추가 되면서 마이그레이션이 필요할 경우 // Realm을 초기화(테이블삭제) 하도록 설정 // allowWritesOnUiThread(true) // 해당 Realm을 UiThread에서 쓰기가 가능하도록 설정 val config: RealmConfiguration = RealmConfiguration.Builder()             .deleteRealmIfMigrationNeeded()             .allowWritesOnUiThread(true)             .build() Realm.setDefaultConfiguration(config)  // Realm 인스턴스 생성 val ream = Realm.getDefaultInstance()
  2. Realm instance를 꺼내서 사용한다.

 

Realm 쿼리

인스턴스를 이용 ream.executeTransaction{ } 람다식으로 Transaction을 수행한다.

람다식의 인자인 it을 이용해서 쿼리를 수행한다.

val ream = Realm.getDefaultInstance()  // Row 생성 = insert ream.executeTransaction{       with(it.createObject(School::class.java)) {         this.name = "서울대" 	      this.location = "서울" 			} }  // select findViewById<Button>(R.id.realm_btn_load).setOnClickListener { 		ream.executeTransaction {         val data = it.where(School::class.java).findFirst() 		    Log.d("data1",data.toString()) 		} }  // delete findViewById<Button>(R.id.realm_btn_delete).setOnClickListener {     ream.executeTransaction { 			  it.where(School::class.java).findAll().deleteFromRealm(0) 		} } 

 

'Android > Android기본' 카테고리의 다른 글

ListView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24
TabLayout, Pager  (0) 2021.11.24
SharedPreference  (0) 2021.11.24
Async  (0) 2021.04.13

 ConstarintLayout 의 높이를 코드상에서 설정해야 하고, 해당 높이를 절대값이 아닌 부모 View의 비율로 설정해야 할 경우.

 

 다른 ViewGroup의 LayoutParams과 다르게 ConstraintLayout LayoutParams에는 matchConstraintPercentHeight 이라는 상대적 높이값을 설정하는 필드가 있다.

 높이를 변경할 ConstraintLayout을 지정하고, Params을 꺼낸 후 matchConstraintPercentHeight 값을 원하는 값(float type)으로 변경하고 적용하면 상대적인 높이가 설정된다.

 

아래는 예시 코드

ConstraintLayout layout = (ConstraintLayout)binding.constraintLayout0;
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams)layout.getLayoutParams();

layoutParams.matchConstraintPercentHeight = 0.5f;

layout.setLayoutParams(layoutParams);
layout.requestLayout();
비동기 처리를 해주는 클래스. deprecated

 

AsyncTask를 상속한다.

onPreExcute 쓰레드 시작 전 작업

doInBackground 쓰레드가 처리 할 작업

onPregressUpdate 중간중간 Main Thread로 돌아옴

onPostExcute 작업을 마친 후 Main Thread로 돌아옴

 

 

Async의 장점

Main Thread(UI Thread)가 sub Thread의 작업을 기다리며 대기하지 않아도 된다.

특히 네트워크를 사용하는 작업에 효율적이다.

 

 

Async의 단점

재사용이 불가능하다. (cancel이 호출된 이후로 다시 사용이 불가.)

Async 객체를 생성한 Activity가 종료되도 자동으로 취소되지 않는다.

AsyncTask는 하나만 실행 가능하다. 여러 인스턴스를 생성해도 하나가 실행이 완료되야 다음 동작이 실행된다. (다수 쓰레스로 병렬 수행이 불가하다)

 

 

 

//

AsyncTask deprecated
class BackgroundAsyncTask(     val progressbar: ProgressBar     ,val progresstext: TextView ): AsyncTask<Int,Int,Int>(){     // params - doInBackground 에서 사용할 타입     // progress - onProgressUpdate 에서 사용할 타입     // result - onPostExecute 에서 사용할 타입     var percent: Int = 0      override fun onPreExecute() {         percent = 0         progressbar.setProgress(percent)     }      override fun doInBackground(vararg params: Int?): Int {         while(isCancelled() == false) {             percent++             if(percent>100) break             else publishProgress(percent)              try {                 Thread.sleep(100)             } catch (e : Exception) {                 e.printStackTrace()             }         }         return percent     }      override fun onProgressUpdate(vararg values: Int?) {         progressbar.setProgress(values[0] ?: 0)         progresstext.setText("Percent : "+values[0])     }      override fun onPostExecute(result: Int?) {         progresstext.setText("Completed!")     }      override fun onCancelled() {         progressbar.setProgress(0)         progresstext.setText("Task canceled")     } }

'Android > Android기본' 카테고리의 다른 글

ListView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24
TabLayout, Pager  (0) 2021.11.24
SharedPreference  (0) 2021.11.24
Realm  (0) 2021.11.24

Premitive type의 첫 글자가 대문자다.

Long, Int, Short, Byte

Double, Float

Char

Boolean

 

 

변수는 가변인 var(Variable), 불변인 val(Value) 두 가지 참조변수 타입이 있다.

var - 값을 수정할 수 있다. 다만, 처음에 넣은 값의 type과 동일해야 한다.

val - 값을 수정할 수 없다.

var, val 선택은 가변성을 고려해 변경이 필요한경우나, 변경에서 안전해야 할 경우 등으로 고려한다. 특히, 코드 중간에 값 변경을 신경쓰지 않고 싶을 경우는 먼저 val로 사용.

 

 

변수 선언

var <name> = <value>

var <name>:<type> = <value>

Kotlin은 값을 넣어주면 자동으로 타입을 선택하지만, 명시적으로 표시해 줄 수도 있다. 안정성을 위해 명시해주는게 좋다.

 

Kotlin은 null safe한 언어인데 null을 허용하지 않는 변수타입에 null값을 대입하려 하면 에러가 발생한다. null을 허용해주고 싶을 때는 명시적으로 타입 뒤에 ? 를 붙인다.

var <name>:<type>? = <value>

 

 

형 변환

변수명.toType() 으로 해당 변수의 타입을 형변할 수 있다.

 

 

 

 

개요

 Interpreter 패턴에서는 프로그램으로 해결해야 하는 어떤 문제를 간단한 미니 프로그램으로 표현하고 미니 프로그램은 그 자체만으론 동작하지 않기 때문에 이를 동작하기 위해 Java 언어로 통역(Interprete)하는 통역 프로그램을 만든다. 여기서 이 통역 프로그램을 인터프리터라 부른다.

 문제에 변경이 발생하면 인터프리터는 그대로 두고 미니 프로그램만을 수정해서 해결한다.

 

 미니 프로그램을 작성하는 미니 언어는 단순한 명령들로 구성되는데 이 명령들을 조합해서 표현하는 것이 미니 언어라 보면 된다. 

 

 미니 프로그램은 미니 언어를 이용해서 작성하는데, 미니 언어에 포함되어 있는 명령들을 조합해서 프로그램을 작성한다. 미니 언어에도 문법이 존재하는데, 

 

역할

AbstractExpression(추상 표현) 역할

 구문 트리 노드의 공통 인터페이스를 결정한다. 예제에서는 Node 클래스가 parse라는 메소드로 공통 인터페이스를 제공.

 

TerminalExpression(종착점 표현) 역할

 BNF의 Terminal Expression에 대응하는 역할로 예제에서는 PrimitiveCommandNode 클래스.

 

NonterminalExpression(비종착점 표현) 역할

 BNF의 Nonterminal Expression에 대응하는 역할. 예제에서는 ProgramNode, CommandNode, RepeatCommandNode, CommandListNode 클래스.

 

Context(문맥, 전후 관계) 역할

 인터프리터가 구문 해석을 실행하귀 위한 정보를 제공하는 역할. 예제에서 Context 클래스.

 

Client 역할

 구문 트리를 조립하기 위해 TerminalExpression과 NonterminalExpression을 호출하는 역할. 예제에서는 Tester 클래스.

 

 

예제 코드

//AbstractExpression 역할
package interpreter;
public abstract class Node {
	public abstract void parse(Context context) throws ParseException;
}


//TerminalExpression 역할
package interpreter;
public class PrimitiveCommandNode extends Node{
	private String name;
	public void parse(Context context) throws ParseException {
		name = context.currentToekn();
		context.skipToken(name);
		if(!name.equals("go") && !name.equals("right") && !name.equals("left")) {
			throw new ParseException(name + " is undefined");
		}
	}
	
	public String toString() {
		return name;
	}
}


//NonterminalExpression 역할
package interpreter;
public class ProgramNode extends Node{
	private Node commandListNode;
	public void parse(Context context) throws ParseException {
		context.skipToken("program");
		commandListNode = new CommandListNode();
		commandListNode.parse(context);
	}
	public String toString() {
		return "[program " + commandListNode + "]";
	}
}

package interpreter;
public class CommandNode extends Node{
	private Node node;
	public void parse(Context context) throws ParseException {
		if(context.currentToekn().equals("repeat")) {
			node = new RepeatCommandNode();
			node.parse(context);
		} else {
			node = new PrimitiveCommandNode();
			node.parse(context);
		}
	}
	
	public String toString() {
		return node.toString();
	}
}


package interpreter;
public class RepeatCommandNode  extends Node{
	private int number;
	private Node commandListNode;
	public void parse(Context context) throws ParseException {
		context.skipToken("repeat");
		number = context.currentNumber();
		context.nextToken();
		commandListNode = new CommandListNode();
		commandListNode.parse(context);
	}
	
	public String toString() {
		return "[repeat" + number + " " + commandListNode + "]";
	}
}


package interpreter;
import java.util.ArrayList;
public class CommandListNode extends Node{
	private ArrayList<Node> list = new ArrayList<>();
	public void parse(Context context) throws ParseException {
		while(true) {
			if(context.currentToekn()==null) {
				throw new ParseException("Missing 'end'");
			} else if (context.currentToekn().equals("end")) {
				context.skipToken("end");
				break;
			} else {
				Node commandNode = new CommandNode();
				commandNode.parse(context);
				list.add(commandNode);
			}
		}
	}
	
	public String toString() {
		return list.toString();
	}
}


//Context 역할
package interpreter;

import java.util.StringTokenizer;

public class Context {
	private StringTokenizer tokenizer;
	private String currentToken;
	
	public Context(String text) {
		tokenizer = new StringTokenizer(text);
		nextToken();
	}
	
	public String nextToken() {
		if(tokenizer.hasMoreTokens()) {
			currentToken = tokenizer.nextToken();
		} else {
			currentToken = null;
		}
		
		return currentToken;
	}
	
	public String currentToekn() {
		return currentToken;
	}
	
	public void skipToken(String token) throws ParseException  {
		if(!token.equals(currentToken)) {
			throw new ParseException("Warning : " + token + " is expected, but " + currentToken + " is found.");
		}
		nextToken();
	}
	
	public int currentNumber() throws ParseException {
		int number = 0;
		try {
			number = Integer.parseInt(currentToken);
		} catch(NumberFormatException e) {
			throw new ParseException("Waring : " + e);
		}
		
		return number;
	}
}



//Client 역할
package interpreter;

import java.io.BufferedReader;
import java.io.FileReader;

public class InterpreterTester {
	public static void main(String[] args) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader("program.txt"));
			String text;
			while((text=reader.readLine()) != null) {
				System.out.println("text = \"" + text + "\"");
				Node node = new ProgramNode();
				node.parse(new Context(text));
				System.out.println("node = " + node); 
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

'디자인 패턴' 카테고리의 다른 글

21. Proxy 패턴  (0) 2021.01.11
20. Flyweight 패턴  (0) 2021.01.05
18. Memento 패턴  (0) 2020.12.30
17. Observer 패턴  (0) 2020.12.29
16. Mediator 패턴  (0) 2020.12.28

+ Recent posts