Overview

Layout that allows the user to swipe left and right through "pages" of content which are usually different fragments. This is a common navigation mode to use instead of ActionBar Tabs with Fragments.

ViewPager

Usage

Layout ViewPager

A ViewPager is a layout which can be added to any layout XML file inside a root layout:

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

If you want an "indicator" that displays the pages available at the top as shown in the screenshot above, you need to include a nested indicator view called a PagerTabStrip:

<android.support.v4.view.ViewPager
   android:id="@+id/vpPager"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">

   <android.support.v4.view.PagerTabStrip
        android:id="@+id/pager_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:paddingBottom="4dp"
        android:paddingTop="4dp" />

</android.support.v4.view.ViewPager>

which will automatically display the page indicator for your pager. You might want to check out the popular ViewPagerIndicator for an improved page indicator.

Define Fragments

Next, let's suppose we have defined two fragments FirstFragment and SecondFragment both of which contain a label in the layout and have implementations such as:

public class FirstFragment extends Fragment {
	// Store instance variables
	private String title;
	private int page;

	// newInstance constructor for creating fragment with arguments
	public static FirstFragment newInstance(int page, String title) {
		FirstFragment fragmentFirst = new FirstFragment();
		Bundle args = new Bundle();
		args.putInt("someInt", page);
		args.putString("someTitle", title);
		fragmentFirst.setArguments(args);
		return fragmentFirst;
	}

	// Store instance variables based on arguments passed
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		page = getArguments().getInt("someInt", 0);
		title = getArguments().getString("someTitle");
	}

	// Inflate the view for the fragment based on layout XML
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, 
            Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.fragment_first, container, false);
		TextView tvLabel = (TextView) view.findViewById(R.id.tvLabel);
		tvLabel.setText(page + " -- " + title);
		return view;
	}
}

Setup FragmentPagerAdapter

Now we need to define the adapter that will properly determine how many pages exist and which fragment to display for each page of the adapter by creating a FragmentPagerAdapter:

public class MainActivity extends AppCompatActivity {
	// ...
	
    public static class MyPagerAdapter extends FragmentPagerAdapter {
	private static int NUM_ITEMS = 3;
		
        public MyPagerAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
        }
        
        // Returns total number of pages
        @Override
        public int getCount() {
            return NUM_ITEMS;
        }
 
        // Returns the fragment to display for that page
        @Override
        public Fragment getItem(int position) {
            switch (position) {
            case 0: // Fragment # 0 - This will show FirstFragment
                return FirstFragment.newInstance(0, "Page # 1");
            case 1: // Fragment # 0 - This will show FirstFragment different title
                return FirstFragment.newInstance(1, "Page # 2");
            case 2: // Fragment # 1 - This will show SecondFragment
                return SecondFragment.newInstance(2, "Page # 3");
            default:
            	return null;
            }
        }
        
        // Returns the page title for the top indicator
        @Override
        public CharSequence getPageTitle(int position) {
        	return "Page " + position;
        }
        
    }

}

For more complex cases with many pages, check out the more dynamic approach with SmartFragmentStatePagerAdapter explained later.

Apply the Adapter

Finally, let's associate the ViewPager with a new instance of our adapter:

public class MainActivity extends AppCompatActivity {
	FragmentPagerAdapter adapterViewPager;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_home);
		ViewPager vpPager = (ViewPager) findViewById(R.id.vpPager);
		adapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
		vpPager.setAdapter(adapterViewPager);
	}
	
	// ...
}

And now we have a basic functioning ViewPager with any number of fragments as pages which can be swiped between.

Selecting or Getting the Page

We can access the selected page within the ViewPager at any time with the getCurrentItem method which returns the current page:

vpPager.getCurrentItem(); // --> 2

The current page can also be changed programmatically with the

vpPager.setCurrentItem(2)

With this getter and setter, we can easily access or modify the selected page at runtime.

Setup OnPageChangeListener

If the Activity needs to be able listen for changes to the page selected or other events surrounding the ViewPager, then we just need to hook into the ViewPager.OnPageChangeListener on the ViewPager to handle the events:

// Attach the page change listener inside the activity
vpPager.setOnPageChangeListener(new OnPageChangeListener() {
	
	// This method will be invoked when a new page becomes selected.
	@Override
	public void onPageSelected(int position) {
		Toast.makeText(HomeActivity.this, 
                    "Selected page position: " + position, Toast.LENGTH_SHORT).show();
	}
	
	// This method will be invoked when the current page is scrolled
	@Override
	public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
		// Code goes here
	}
	
	// Called when the scroll state changes: 
	// SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING, SCROLL_STATE_SETTLING
	@Override
	public void onPageScrollStateChanged(int state) {
		// Code goes here
	}
});
Note: if you're using the PagerSlidingTabStrip library (see below), call setOnPageChangeListener on your PagerSlidingTabStrip object instead of on your ViewPager. You must do this because the ViewPager only supports notifying one listener.

Tabbed Interface with Pager

We can use the ViewPager to display a tabbed indicator in order to create tabs to display our fragments. At Google I/O 2015, Google announced a new TabLayout class that makes creating this tabbed interface fairly easy to do. See Google Play Style Tabs using TabLayout for a walkthrough.

An alternative approach to achieve this is to use the third-party PagerSlidingTabStrip library.

Tabs

In this way, we can use the same pager system described above and augment the pager with a tabbed navigation indicator.

Dynamic ViewPager Fragments

In certain cases, we may require a dynamic ViewPager where we want to get access to fragment instances or with pages being added or removed at runtime. If your ViewPager is more dynamic with many pages and fragments, we will want to use an implementation of the alternate FragmentStatePagerAdapter instead. Below shows us how to use this and also intelligently cache the fragments for easy lookup.

Setup SmartFragmentStatePagerAdapter

First, copy in the SmartFragmentStatePagerAdapter.java which provides the intelligent caching of registered fragments within our ViewPager. This solves the common problem of needing to access the current item within the ViewPager.

Now, we want to extend from SmartFragmentStatePagerAdapter copied above when declaring our adapter so we can take advantage of the better memory management of the state pager:

public class MainActivity extends AppCompatActivity {
    // ...
    private SmartFragmentStatePagerAdapter adapterViewPager;
    
    // Extend from SmartFragmentStatePagerAdapter now instead for more dynamic ViewPager items
    public static class MyPagerAdapter extends SmartFragmentStatePagerAdapter {
	private static int NUM_ITEMS = 3;
		
        public MyPagerAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
        }
        
        // Returns total number of pages
        @Override
        public int getCount() {
            return NUM_ITEMS;
        }
 
        // Returns the fragment to display for that page
        @Override
        public Fragment getItem(int position) {
            switch (position) {
            case 0: // Fragment # 0 - This will show FirstFragment
                return FirstFragment.newInstance(0, "Page # 1");
            case 1: // Fragment # 0 - This will show FirstFragment different title
                return FirstFragment.newInstance(1, "Page # 2");
            case 2: // Fragment # 1 - This will show SecondFragment
                return SecondFragment.newInstance(2, "Page # 3");
            default:
            	return null;
            }
        }
        
        // Returns the page title for the top indicator
        @Override
        public CharSequence getPageTitle(int position) {
        	return "Page " + position;
        }
        
    }

}

Access Fragment Instances

Now with this adapter in place, we can also easily access any fragments within the ViewPager with:

adapterViewPager.getRegisteredFragment(0); 
// returns first Fragment item within the pager

and we can easily access the "current" pager item with:

adapterViewPager.getRegisteredFragment(vpPager.getCurrentItem());
// returns current Fragment item displayed within the pager

This pattern should save your app quite a deal of memory and allow for much easier management of fragments within your pager for the right situation.

Set Offscreen Page Limit

Alternatively, you can use the method setOffscreenPageLimit(int limit) provided by ViewPager to set how many page instances you want the system to keep in memory on either side of your current page. As a result, more memory will be consumed. So be careful when tweaking this setting if your pages have complex layouts.

For example, to let the system keep 3 page instances on both sides of the current page:

vpPager.setOffscreenPageLimit(3);

This can be useful in order to preload more fragments for a smoother viewing experience trading off with memory usage.

ViewPager with Visible Adjacent Pages

If you are interested in a ViewPager with visible adjacent pages that are partially visible:

ViewPager Adjacent

We can do that with by tuning a few properties of our pager. First, here's how the ViewPager might be defined in the XML Layout:

<android.support.v4.view.ViewPager
  	android:id="@+id/pager"
  	android:gravity="center"
  	android:layout_width="match_parent"
  	android:layout_height="0px"
  	android:paddingLeft="24dp"
  	android:paddingRight="12dp"
  	android:layout_weight="1" />

Next, we need to tune these properties of the pager in the containing fragment or activity:

ViewPager vpPager = (ViewPager) view.findViewById(R.id.vpPager);
vpPager.setClipToPadding(false);
vpPager.setPageMargin(12);
// Now setup the adapter as normal

Finally we need to adjust the width inside the adapter:

class MyPageAdapter : FragmentStatePagerAdapter {
    @Override
    public float getPageWidth (int position) {
        return 0.93f;
    }	
    
    // ...
}

For more details, you can follow these guides:

Animating the Scroll with PageTransformer

We can customize how the pages animate as they are being swiped between using the PageTransformer. This transformer exists within the support library and is compatible with API 11 or greater. Usage is pretty straightforward, just attach a PageTransformer to the ViewPager:

vpPager.setPageTransformer(false, new ViewPager.PageTransformer() { 
    @Override
    public void transformPage(View page, float position) {
        // Do our transformations to the pages here
    }
});

The first argument is set to true if the supplied PageTransformer requires page views to be drawn from last to first instead of first to last. The second argument is the transformer which requires defining the transformPage method to define the sliding page behavior.

The transformPage method accepts two parameters: page which is the particular page to be modified and position which indicates where a given page is located relative to the center of the screen. The page which fills the screen is at position 0. The page immediately to the right is at position 1. If the user scrolls halfway between pages one and two, page one has a position of -0.5 and page two has a position of 0.5.

vpPager.setPageTransformer(false, new ViewPager.PageTransformer() { 
    @Override
    public void transformPage(View page, float position) {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);
        } else if(position <= 1){ // Page to the left, page centered, page to the right
           // modify page view animations here for pages in view 
        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
});

For more details, check out the official guide or this guide. You can also review this cool rotating page transformer effect for another example.

Disabling Swipe Events

In certain situations your app might want to have a ViewPager that allows switching pages using an indicator but that doesn't intercept swipe events. This is usually because we want to have the swipe events perform another action rather than change the page.

The first step is to define a custom ViewPager subclass called LockableViewPager. The class inherits from ViewPager and includes a new method called setSwipeable to control if swipe events are enabled or not. Copy this class into your project. Make sure to change your layout file accordingly:

<mypackage.lockableviewpager
    android:id="@+id/photosViewPager" 
    android:layout_height="match_parent" 
    android:layout_width="match_parent" />

Now, just call setSwipeable(false) to disable swiping to change the page.

Launching an Activity with Tab Selected

Often when launching a tabbed activity, there needs to be a way to select a particular tab to be displayed once the activity loads. For example, an activity has three tabs with one tab being a list of created posts. After a user creates a post on a separate activity, the user needs to be returned to the main activity with the "new posts" tab displayed. This can be done through the use of intent extras and the ViewPager#setCurrentItem method. First, when launching the tabbed activity, we need to pass in the selected tab as an extra:

/* In creation activity that wants to launch a tabbed activity */
Intent intent = new Intent(this, MyTabbedActivity.class);
// Pass in tab to be displayed
i.putExtra(MyTabbedActivity.SELECTED_TAB_EXTRA_KEY, MyTabbedActivity.NEW_POSTS_TAB);
// Start the activity
startActivity(i);

If the activity needs to return a result, we can also return this as an activity result. Next, we can read this information from the intent within the tabbed activity:

/* In tabbed activity */
public final static int SELECTED_TAB_EXTRA_KEY = "selectedTabIndex";
public final static int HOME_TAB = 0;
public final static int FAVORITES_TAB = 1;
public final static int NEW_POSTS_TAB = 2;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
    // Set the selected tab
    setSelectedTab();
}

// Reads selected tab from launching intent and 
// sets page accordingly
public void setSelectedTab() {
   // Fetch the selected tab index with default
   int selectedTabIndex = getIntent().getIntExtra(SELECTED_TAB_EXTRA_KEY, HOME_TAB); 
   // Switch to page based on index
   vpPager.setCurrentItem(selectedTabIndex);
}

With that, any activity can launch the tabbed activity with the ability to configure the selected tab.

Custom Pages without Fragments

While a ViewPager is often coupled with a Fragment for each page using the FragmentPagerAdapter, there are cases where the pages are better off as plain views.

A good example is an image gallery, where the user can swipe between different pictures. To achieve this, we can extend from PagerAdapter:

// Custom pager adapter not using fragments
class CustomPagerAdapter extends PagerAdapter {
 
    Context mContext;
    LayoutInflater mLayoutInflater;
    ArrayList<Page> pages = new ArrayList<>();
 
    public CustomPagerAdapter(Context context) {
        mContext = context;
        mLayoutInflater = LayoutInflater.from(mContext);
    }
 
    // Returns the number of pages to be displayed in the ViewPager.
    @Override
    public int getCount() {
        return pages.size();
    }
 
    // Returns true if a particular object (page) is from a particular page
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }
 
    // This method should create the page for the given position passed to it as an argument. 
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // Inflate the layout for the page
        View itemView = mLayoutInflater.inflate(R.layout.pager_item, container, false);
        // Find and populate data into the page (i.e set the image)
        ImageView imageView = (ImageView) itemView.findViewById(R.id.imageView);
        // ...
        // Add the page to the container
        container.addView(itemView);
        // Return the page
        return itemView;
    }
 
    // Removes the page from the container for the given position.
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }
}

This is most commonly used for image slideshows or galleries. See this image gallery tutorial or this viewpager without fragments guide for more detailed steps.

References

android UI 모음 사이트

 

링크주소:https://github.com/wasabeef/awesome-android-ui

TableLayout columns equal width

 

<tablelayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:stretchColumns="1"> 
  
    <TableRow>
      <!-- Column 1 -->
      <TextView
         android:id="@+id/tbl_txt1"
         android:layout_width="0dip"
         android:layout_height="wrap_content"
         android:background="@color/red"
         android:textColor="@color/white"
         android:padding="10dip"
         android:layout_margin="4dip"
         android:layout_weight="1"
         android:text="Column 1" />
          
      <!-- Column 2 -->
      <TextView
         android:id="@+id/tbl_txt2"
         android:layout_width="0dip"
         android:layout_height="wrap_content"
         android:background="@color/red"
         android:textColor="@color/white"
         android:padding="10dip"
         android:layout_margin="4dip"
         android:layout_weight="1"
         android:text="Column 2" />
          
      <!-- Column 3 -->
      <TextView
         android:id="@+id/tbl_txt3"
         android:layout_width="0dip"
         android:layout_height="wrap_content"
         android:background="@color/red"
         android:textColor="@color/white"
         android:padding="10dip"
         android:layout_margin="4dip"
         android:layout_weight="1"
         android:text="Column 3" />
    </TableRow>
</TableLayout>

1. 안드로이드 테이블레이아웃(tablelayout) 테두리(border) 넣기

 

border.xml

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

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

<stroke
android:width="1dp"
android:color="#ff888888" />

<solid android:color="#ffffff" />
<padding
android:bottom="8dp"
android:left="8dp"
android:right="6dp"
android:top="8dp" />


</shape>

이런식으로 drawble경로에 하나 만들어서 테이블레이아웃의 background에 적용

2. 테이블레이아웃 selector 만들기

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

<item android:state_enabled="true" android:state_pressed="false">
<shape>
<stroke android:width="1dp" android:color="#ff888888" />

<solid android:color="#ffffff" />
</shape>
</item>

<item android:state_enabled="true" android:state_pressed="true">
<shape>
<stroke android:width="1dp" android:color="#ff888888" />

<solid android:color="#ff888888" />
</shape>
</item>


</selector>



 

어플리케이션에서 디자이너의 능력이 한껏 발휘되는 다른 부분 중 또하나의 중요한 부분이 바로 안드로이드 앱의 아이콘이다.

앞서 스플래시 이미지를 만들었다고 하면 이제는 어플리케이션을 대표하는 중요한 요소인 '아이콘'을 다뤄보려 한다.


우리는 안드로이드 운영체제 위에 올라가있는 소프트웨어(앱)를 실행시키기 위해 아이콘을 클릭하고,

메시지 등의 푸쉬 알람을 수신할 때 상태바(status bar)에는 아이콘을 이용해 새로운 소식이 왔음을 확인하며,

어플리케이션 내에서는 메뉴, 탭, 다이얼로그, 리스트 등에도 아이콘이 다양하게 활용되어 UI를 장식한다.


안드로이드의 아이콘 가이드라인은 iOS(아이폰)의 아이콘만큼 깐깐하게 지킬필요는 없지만

많은 부분은 디자이너들이 그냥 이쁘게만 만들다보니 놓치게 되고 개발자가 쉽게 파악하지 못하는 경우가 생긴다.

아래의 아이콘 디자인 가이드라인과 팁을 따라서 정갈하고 정해진 틀을 지켜 앱을 대표하고 꾸미는 중요한 요소인 아이콘을 디자인하게 되면

그 앱은 아이콘이 없이 텍스트로만 이루어져있거나 미적 요소가 없는 단순한 앱보다 훨씬 풍부한 감각적 요소를 가미할 수 있을 것이다.



안드로이드 앱 아이콘/아이콘 디자인/안드로이드 디자인/UI 디자인/앱 아이콘 디자인/어플리케이션 디자인






사용자 인터페이스를 통해 통합된 모양과 느낌을 만드는 것은 당신의 앱(생산물)에 가치를 더하는 역할을 한다.

그래픽 스타일을 효율적으로 사용하게 되면 당신 앱 UI가 사용자들에게 훨씬 전문적으로 보이게 만들어 준다.

여기서는 안드로이드 2.x의 프레임워크에서 사용하는 일반적인 스타일과 매치되는 앱의 UI의 다양한 부분에서 아이콘을 만드는데

필요한 정보들을 제공할 것이고, 이 가이드라인을 따르게 되면 사용자는 멋지고 통합된 UX를 느낄 수 있을 것이다.



우선, 안드로이드 앱 전체에서 사용되는 아이콘의 일반적인 타입들에 대해서 자세한 설명을 해 보도록 하자.

자세한 내용이 보고싶으면 각 아이콘 설명 아래 있는 링크를 따라가면 된다, 제작하는 가이드라인이 구체적으로 제시되어 있다.





Launcher Icons :: 런쳐 아이콘

런처 아이콘은 디바이스의 홈스크린과 런쳐 윈도우에서 당신의 앱을 표현하는 그래픽 아이콘이다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_launcher.html





Menu Icons :: 메뉴 아이콘

메뉴 아이콘은 사용자들이 메뉴 버튼을 눌렀을 때 유저에게 옵션 메뉴에서 보여줄 그래픽 아이콘이다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_menu.html





Action Bar Icons :: 액션바 아이콘

액션바 아이콘은 액션바 상에서 액션 항목을 표현하기 위한 그래픽 아이콘이다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_action_bar.html





Status Bar Icons :: 상태바 아이콘

상태바 아이콘은 당신의 앱으로부터 전송된 알람을 상태바에 표현하기 위해 사용되는 그래픽 아이콘이다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_status_bar.html





Tab Icons :: 탭 아이콘

탭 아이콘은 멀티 탭 인터페이스 사용 시 각각의 탭에 들어갈 그래픽 아이콘이다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_tab.html





Dialog Icons :: 다이얼로그 아이콘

다이얼로그 아이콘은 사용자의 상호작용을 위한 팝업 다이얼로그 박스에 보여질 아이콘이다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_dialog.html





List View Icons :: 리스트뷰 아이콘

리스트뷰 아이콘이란 리스트 아이템들을 리스트 뷰 상에서 표현되는 아이콘이다. 설정 어플리케이션이 그 예다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design_list.html









안드로이드 아이콘 템플릿 팩이란 가이드라인에 맞도록 아이콘을 쉽게 제작할 수 있게 도와주기 위해

디자인, 텍스쳐, 레이어 스타일 등을 모아놓은 하나의 패키지를 말하며, 이는 안드로이드 공식 사이트에서 제공하고 있다.


icon_templates-v4.0.zip

(http://developer.android.com/shareables/icon_templates-v4.0.zip)



안드로이드 공식 사이트에서는 아이콘을 디자인하기 이전에 템플릿 팩을 다운로드 받아 사용하는 것을 권장하고 있다.

psd형태로 제공되는 아이콘 템플릿들은 안드로이드 플랫폼의 아이콘을 제작하는데 필요한 레이어와 디자인 처리 방식등을 포함하고 있다.






안드로이드는 다양하고 넓은 범위의 스크린 사이즈와 해상도를 제공하는 여러 기기들에서 돌아가도록 설계된다.

만약 어플리케이션의 아이콘을 디자인하게 된다면, 이역시 여러 해상도의 각기 다른 기기에서 보여지게 될 것이라는 점을 기억해야 한다.

멀티플 스크린 지원 문서(링크)에 나와있듯이 안드로이드 플랫폼은 디바이스의 스크린 사이즈와 해상도에 상관없이

어떤 기기에서든 우리가 원하는 방식, 원하는 모양 그대로 적절하게 아이콘들이 표시되어야 한다.


일반적으로, 아이콘들을 각 스크린 밀도에 따라 각각 일반화된 형태의 아이콘 세트를 제공하는 방식이 추천된다.

그 이후에 그들을 각각 해상도별로 다른 리소스 폴더에 넣고 어플리케이션을 구동시키는 것이다.


어플리케이션이 실행되면, 안드로이드 기기는 실행되는 기기의 스크린 특징을 체크한 이후에

그 사이즈나 해상도에 맞는 적절한 안드로이드 아이콘을 로딩해와서 보여주게 되는 방식인 것이다.

어떻게 밀도별로 다른 리소스를 어플리케이션에 삽입하는지 잘 모르겠다면, 아래의 링크를 따라가서 확인하기 바란다.


Resource directory qualifiers for screen size and density

http://developer.android.com/guide/practices/screens_support.html#qualifiers







이제까지 아이콘의 종류와 제공방식에 대해 설명했다면,

여기서 말하는 '아이콘 디자인 팁'은 훨씬 원활하고 정형화된 디자인 작업으로 가독성 높고 팀단위로 일할때도 유용한

디자이너를 위한, 그리고 팀을 위한 아이콘 디자인 방법에 대해 조금 더 자세히 얘기해 줄 것이다.




Use common naming conventions for icon assets

제작된 아이콘들에 대해 일반적(규칙적)인 명명 규칙을 사용하라.


아이콘을 저장할 때, 이미지 파일들의 이름이 관련있는 것들끼리 디렉토리 내에서 그룹핑될 수 있도록 정하라.

특히 이것은 일반적인 prefix를 사용하여 각각의 아이콘 형태를 나타내는데 매우 유용하다.

이는 반드시 해야만 하는 것은 아니지만, 규칙을 지정해서 관리가 수월하게 하기 위한 하나의 '팁'이다.






Set up a working space that organizes files for multiple densities

여러개의 밀도를 가진 파일들을 정리하기 위한 작업 공간을 설정하라.


여러가지 해상도에서의 스크린 밀도를 지원한다는 것은, 같은 아이콘을 여러개의 다른 해상도 버전으로 만들어야 한다는 것을 의미한다.

다른 해상도의 파일 복사본들을 관리하거나 찾기 쉽게 하기 위해서, 해상도별로 파일들을 분류하는 디렉토리를 만드는 것을 추천한다.



이런 구조를 이용하면 특정한 밀도 구조에 맞춰서 앱 리소스에 완성된 아이콘들을 저장할 수 있게 된다.

당신의 작업 공간(워크 스페이스)의 구조는 실제 어플리케이션의 리소스 구조(안드로이드 개발의 디렉토리)와 매우 비슷하기 때문에,

이런 구조를 이용한다면 쉽게 어떤 아이콘들이 어플리케이션의 리소스 디렉토리로 복사되어야 하는지 결정할 수 있다.


이렇게 그들의 밀도에 따라서 제작된 결과물들을 저장하는 것은 디자이너 혹은 개발자가 쉽게 파일이름으로 구별이 가능하고

이는 다른 밀도이지만 동일한 파일 이름을 공유해야하는 경우를 쉽게 해결할 수 있다. 이는 매우 중요한 역할을 한다.


비교를 위해, 아래 나온 디렉토리 구조는 전형적인 어플리케이션 상에서의 모습을 나타낸 것이다.







Use vector shapes where possible

가능한 곳이라면 벡터를 이용하라.


포토샵과 같은 많은 이미지 편집 툴에서 벡터 요소와 래스터 레이어, 효과들을 조합하여 사용할 수 있다.

가능한 곳이라면 디테일이나 가장자리의 비트 손실 없이 사이즈 조절이 가능하도록 벡터 도형들을 사용하라.

벡터를 사용하면 작은 해상도에서도 픽셀 경계에 있는 가장자리나 모서리를 쉽게 처리할 수 있다.





Start with large artboards

처음에는 큰 그림으로 시작하라.


디자이너는 아이콘을 제작하면서 다양한 스크린 밀도에 따라 리소스를 생성해 내야 한다.

이렇기 때문에 아이콘의 가로세로 비율에 맞는 큰 artboard에서 아이콘 디자인을 시작하는 것이 가장 좋다.

예를 들어 런쳐 아이콘이 화면 밀도에 따라 96, 72, 48, 36 픽셀인데 처음에 864*864 artboard에서 런쳐 아이콘을 그리게 되면

최종적으로 만들어낼 런쳐 아이콘의 대상 크기로 줄여서 사용하게 되면 훨씬 쉽고 분명해 질 수 있는 것이다.





When scaling, redraw bitmap layers as needed

확대를 할 때에, 비트맵 레이어는 필요에 따라 다시 그려라.


벡터 레이어가 아닌 비트맵 레이어에서 이미지의 크기를 조정하게 되면

해당 레이어가 높은 밀도에서도 선명하게 보여질 수 있도록 직접 다시 그려야 할 필요성이 있다.

예를 들어 60*60 사이즈의 원이 mdpi 해상도에 맞춰서 비트맵에 그려졌다면 

새로 필요하게 된 hdpi의 90*90 사이즈 리소스는 새로 그려져야 한다.





When saving image assets, remove unnecessary metadata

이미지를 저장할 때에, 불필요한 메타데이터는 모두 제거하라.


안드로이드 SDK 툴에서 앱 리소스들을 기기가 알아들을 수 있는 이진 코드(binary)로 변환할 때 자체적으로 png를 압축하긴 하지만

더 좋은 방식은 PNG파일의 제작을 마치고 나서 불필요한 헤더나 메타데이터를 없애주는 것이다.

OptiPNG나 Pngcrush 등의 툴을 이용하면 메타데이터들을 제거할 수 있고 이미지 파일의 사이즈도 최적화 할 수 있다.





Make sure that corresponding assets for different densities use the same filenames

같은 이미지의 다른 밀도인 파일들이 같은 파일 이름을 사용하고 있는지 확인하라.


같은 이미지의, 하지만 다른 밀도를 가진 아이콘 파일은 해상도만 다를 뿐 동일한 파일 이름을 사용해야 한다.

하지만 밀도 별 리소스 디렉토리에 저장하기 때문에 파일 이름이 같다고 해서 충돌이 일어나는 일은 '당연히' 없다.

이는 안드로이드에서 지원하는 것이며 위에서 이미 언급했듯이 각 기기의 화면에 따라 시스템이 적절한 리소스를 찾아 로드한다.

이러한 이유로 각 디렉토리에 있는 이미지들의 집합은 일관성을 유지해야 하고 특정한 접미사가 붙어있어서는 안된다.


references


Android - Icon Design Guidelines

http://developer.android.com/guide/practices/ui_guidelines/icon_design.html

출처:http://baekhorang.tistory.com/entry/


● Composite 중에서 GridView를 살펴보겠습니다. 이것 역시 다른 객체와 함께 사용해서 화면을 구성하게 되는데요. 지난 포스팅에서 작성한 리스트뷰가 행으로 아이템을 출력했다면 이것은  가로, 세로 2차원으로 아이템을 배치하고 스크롤하면서 아이템을 탐색할 수 있습니다. 이것 또한 "ListAdapter"를 이용하여 매핑하는 방식으로 구현을 하는데요. 


예제를 하나 만들면서 자세히 보겠습니다.

 

 

● 간단한 프로젝트를 하나 만들건데요. 위 그림처럼 단말기에 있는 앱의 아이콘과 이름을 아이템으로 그리드뷰로 출력하고, 아이콘을 클릭하면 앱에 대한 설명이 Toast를 통해 출력되고 어플이 실행되며, "뒤로" 버튼을 누르면 다시 돌아오는 기능을 구현하겠습니다. 즉 내가 만든 어플(GrideView)에서 "인텐트"를 이용해서 다른 어플을 실행할 수 있는점을 살펴보겠습니다.

 

● 이 프로젝트는 세개의 파일이 필요하겠네요. activity_mainj.xml 파일에는 그리드뷰 를 배치하고, item.xml파일을 하나 만들어서 출력할 아이템에 대한 레이아웃을 정의하고, MainActivity.java에서 단말기의 어플 목록을 가져와서 그리드뷰에 출력하는 데이터셋으로 이용하고, 이것들을 매핑하는 어댑터 클래스를 생성하여 적용하게 됩니다.

 

● 먼저 activity_main.xml파일에" GridView"를 배치하겠습니다.

 

 

● xml 소스코드를 살펴보겠습니다.

 

 

android:numColumns를 "auto_fit"으로 설정하여 아이템의 크기에 따라 자동으로 '열'의 수가 조정되게 하고 있구요. android:stretchMode를 "columnWidth"로 설정하여 아이템들의 '열' 폭을 자동으로 조절되게 하고 있습니다.

 

 

● 다음으로 그리드뷰에 매핑할 아이템의 레이아웃을 정의할 item.xml파일을 작성하겠습니다. 여기에는 어플의아이콘을 출력할 ImageView와 이름을 출력할 TextView가 배치가 됩니다.

 

이미지뷰의 크기를 큰 아이콘의 크기인 "72px"로 정의하고 있으며, 이름을 출력할 TextView가 중앙에 배치되도록 layout_gravity를 "center"로 설정하고, 텍스트뷰안에 있는 글자들이 중앙에 배치되도록 gravity를 "center"로 설정하고 있습니다. lines="1"은 이름이 한줄로만 표시되도록 하는 것이고, ellipsize를 "end"로 설정하는 것은 이름이 길때 "..."으로 줄임표시 하기 위한 것입니다.

 

 

 

● 이제 단말기에 설치된 어플 목록을 가져와서 실행하고, 그리드뷰에  목록을 출력하는 내용을 MainActivity.java파일에 작성하겠습니다.

 이 클래스에는 또 다른 클래스(gridAdapter)가 작성이 될것입니다. 하위 클래스에서도 사용할 수 있도록 액티비티, GridView, 앱 정보를 기록할 List, 앱을 제어할 PackageManager 변수들을 전역변수로 선언하고 있습니다.

 

 

● 다음으로 Intent와 어댑터 클래스를 작성합니다.

위 내용중 첫번째 박스의 경우 인텐트를 통해서 앱의 목록을 가져오기 위해서 "Intent.ACTION_MAIN"으로 인텐트를 생성하고, 실행 정보를 얻기 위해 "Intent.CATEGORY_LAUNCHER"를 카테고리로 추가하고 있습니다. 패키지 관리자를 호출해서 앱 정보를 요청하고 이것을 "app"객체에 담고 있으며, 그리드뷰 객체를 찾아와서 아래쪽 클래스에서 정의할 목록 어댑터를 적용하고 있습니다.

 

다음으로 두번째 박스에서는 위쪽에서 적용한 gridAdapter 클래스를  BaseAdapter클래스를 상속받아서  작성합니다. 아이템 레이아웃 파일을 매핑하기 위해서 LayoutInflater객체를 선언하고 생성자를 정의하고, 출력할 목록 수 계산, 아이템을 호출하는 메서드, 아이템의 아이디를 구할 메서드를 정의하고 있습니다.

 

 

 

 

● 다음으로 그리드뷰에 앱 정보와 아이템 레이아웃을 매핑하는 메서드를 작성하겠습니다.

 

1. 아이템뷰의 아이템 레이아웃을 재활용 하는 방식으로 구현하고 있는데요. 이 메서드에서 아이템뷰는 "contvertView"에 해당이 되는데, 이 객체가 아직 생성이 되지 않은 경우에는 LayoutInflater를 이용하여 item.xml파일을 호출하여 아이템뷰의 객체를 생성하도록 하고 있습니다.

 

2 위에서 구한 앱목록(apps)에서 이 메서드에서 매핑할 한개의 어플 정보만 추출하고 ResolveInfo형의 "info"라는 객체에 기록하고 있습니다. 이 정보는 아래쪽에서 클릭이벤트에서도 사용하기 위해서 final로 선언을 하고 있습니다.

 

3. ImageView, TextView 객체를 찾아서 각각 아이콘과 이름을 대입하고 있습니다.

 

4. 이미지뷰를 클릭하면 해당 앱이 실행되도록 클릭 이벤트를 작성하고 있는데요. "Intent.ACTION_RUN"으로 인텐트를 생성하고, "ComponentName"으로 해당 어플 실행 객체를 불러와서 그 어플의 액티비티를 실행하도록 하고 있습니다. 이렇게 하면 현재 만들고 있는 앱에서 다른 앱들을 실행을 할 수 있게 됩니다. 그리고 "BACK" 버튼을 누르면 다시 GridView 어플로 돌아오게 하고 있습니다. 여기서 알 수 있는 한가지는 어떤 어플을 실행하기 위해서는 그 앱의 패키지 정보와 시작하는 액티비티 클래스의 이름을 알아야 하는데, 이것들을 ResolveInfo.activityInfo를 통해서 구할 수 있다는 점입니다.

마지막으로 새로 실행시키는 앱의 정보(패키지이름, 액티비티 클래스 이름)를 Toast를 이용해서 출력하도록 하고 있습니다.

 

 

 

● 이제 AVD에서 실행을 해보겠습니다.

 

 

 ● 지금까지 만든 GridView 앱을 실행을 해보면 위와 같이 AVD에 설치되어 있는 어플들의 목록이 나타나게 되고, 이것들을 클릭하면 "Toast"를 통해서 그 앱의 패키지 이름과 액티비티 클래스 이름이 출력이 되면서 실행을 하게 됩니다. 실행을 하다가 "뒤로" 버튼을 누르면 다시 돌아오는 것을 확인 할 수 있습니다. 이부분에서인텐트가 자신의 앱에서만 적용되는 것이 아니라  다른 앱까지도 적용이 된다는 것을 알 수 있습니다.

 Pixel Densities

Android icons require five separate sizes for different screen pixel densities. Icons for lower resolution are created automatically from the baseline.

mdpi (Baseline):

160 dpi

hdpi:

240 dpi

1.5×

xhdpi:

320 dpi

xxhdpi:

490 dpi

xxxhdpi:

640 dpi

 

 

Sizes (px)

Format and naming

Notes

 Launcher icons

 48 × 48 (mdpi)

 .png

 Three-dimensional, front view, with a slight perspective as if viewed from above, so that users perceive some depth.

 72 × 72 (hdpi)

 96 × 96 (xhdpi)

 144 × 144 (xxhdpi)
 192 × 192 (xxxhdpi)
 512 × 512 (Google Play store)
    

 Action bar, Dialog & Tab icons

 24 × 24 area in 32 × 32 (mdpi)

 png

These icons are used in the action bar menu. The first number is the size of the icon area, and the second is file size.

 36 × 36 area in 48 × 48 (hdpi)

 48 × 48 area in 64 × 64 (xhdpi)

 72 × 72 area in 96 × 96 (xxhdpi)

 96 × 96 area in 128 × 128 (xxxhdpi)

 

 Small Contextual Icons

 16 × 16 (mdpi)

 png

Small icons are used to surface actions and/or provide status for specific items. For example, in the Gmail app, each message has a star icon that marks the message as important.
 24 × 24 (hdpi)
 32 × 32 (xhdpi)
 48 × 48 (xxhdpi)
 64 × 64 (xxxhdpi)

 Notification icons

 22 × 22 area in 24 × 24 (mdpi)

 png

These are used to represent application notifications in the status bar. They should be flat (no gradients), white and face-on perspective

 33 × 33 area in 36 × 36 (hdpi)
 44 × 44 area in 48 × 48 (xhdpi)
 66 × 66 area in 72 × 72 (xxhdpi)

 88 × 88 area in 96 × 96 (xxxhdpi)

+ Recent posts