Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Master your custom views
Search
Rui Gonçalo
October 18, 2017
Programming
1
140
Master your custom views
Real examples of how to create pixel perfect views that will make your design team happy :)
Rui Gonçalo
October 18, 2017
Tweet
Share
More Decks by Rui Gonçalo
See All by Rui Gonçalo
NYC Android meetup May 2019
ruigoncalo
0
34
Start speking it @ Droidcon Krakow 2017
ruigoncalo
0
120
Espressing yourself
ruigoncalo
0
68
Other Decks in Programming
See All in Programming
コマンドとリード間の連携に対する脅威分析フレームワーク
pandayumi
1
430
AI Agent Dojo #4: watsonx Orchestrate ADK体験
oniak3ibm
PRO
0
140
Pythonではじめるオープンデータ分析〜書籍の紹介と書籍で紹介しきれなかった事例の紹介〜
welliving
3
860
16年目のピクシブ百科事典を支える最新の技術基盤 / The Modern Tech Stack Powering Pixiv Encyclopedia in its 16th Year
ahuglajbclajep
5
940
AIエージェント、”どう作るか”で差は出るか? / AI Agents: Does the "How" Make a Difference?
rkaga
4
1.9k
副作用をどこに置くか問題:オブジェクト指向で整理する設計判断ツリー
koxya
1
560
Spinner 軸ズレ現象を調べたらレンダリング深淵に飲まれた #レバテックMeetup
bengo4com
1
220
高速開発のためのコード整理術
sutetotanuki
1
350
re:Invent 2025 トレンドからみる製品開発への AI Agent 活用
yoskoh
0
710
なぜSQLはAIぽく見えるのか/why does SQL look AI like
florets1
0
410
Vibe codingでおすすめの言語と開発手法
uyuki234
0
210
フロントエンド開発の勘所 -複数事業を経験して見えた判断軸の違い-
heimusu
7
2.7k
Featured
See All Featured
The SEO Collaboration Effect
kristinabergwall1
0
340
Scaling GitHub
holman
464
140k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
1
1.3k
Testing 201, or: Great Expectations
jmmastey
46
8k
WCS-LA-2024
lcolladotor
0
430
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
First, design no harm
axbom
PRO
2
1.1k
Building Applications with DynamoDB
mza
96
6.9k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
310
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.7k
Navigating Weather and Climate Data
rabernat
0
82
Transcript
Master your custom views @rmgoncalo Rui Gonçalo
Screens
Screen size: physical size (diagonal) e.g: 5.5’’
Screen size: physical size (diagonal) e.g: 5.5’’ Resolution: # of
physical pixels on a screen e.g: 1080 pixels x 1920 pixels
Screen size: physical size (diagonal) e.g: 5.5’’ Screen density: #
of pixels within a physical area e.g: 160 dots per inch (dpi) Resolution: # of physical pixels on a screen e.g: 1080 pixels x 1920 pixels
Density-independent pixel (dp): virtual pixel that abstracts different density screens
1 dp = 1 pixel on 160 dpi
None
resolution 1080x1920 density xxhdpi 300dp x 100dp 250dp x 100dp
200dp x 100dp 150dp x 100dp
300dp x 100dp 250dp x 100dp 200dp x 100dp 150dp
x 100dp
300dp x 100dp 250dp x 100dp 200dp x 100dp 150dp
x 100dp resolution 1080x1920 density xxhdpi resolution 1080x1920 density xxhdpi
resources.displayMetrics.density resolution 1080x1920 density xxhdpi
2.6 resolution 1080x1920 density xxhdpi resources.displayMetrics.density
2.6 160 * 2.6 = 416 dpi width 1080 /
2.6 ~ 411 dp height 1920 / 2.6 ~ 731 dp resolution 1080x1920 density xxhdpi resources.displayMetrics.density
2.6 300 dp * 2.6 = 780 px 250 dp
* 2.6 = 650 px 780px 650px 520px 390px 200 dp * 2.6 = 520 px 150 dp * 2.6 = 390 px 160 * 2.6 = 416 dpi width 1080 / 2.6 ~ 411 dp height 1920 / 2.6 ~ 731 dp resolution 1080x1920 density xxhdpi resources.displayMetrics.density
resources.displayMetrics.density resolution 1080x1920 density xxhdpi
3.0 160 * 3.0 = 480 dpi width 1080 /
3.0 ~ 360 dp height 1920 / 3.0 ~ 640 dp resolution 1080x1920 density xxhdpi resources.displayMetrics.density
3.0 300 dp * 3.0 = 900 px 250 dp
* 3.0 = 750 px 200 dp * 3.0 = 600 px 150 dp * 3.0 = 450 px 900px 750px 600px 450px 160 * 3.0 = 480 dpi width 1080 / 3.0 ~ 360 dp height 1920 / 3.0 ~ 640 dp resolution 1080x1920 density xxhdpi resources.displayMetrics.density
resolution 1080x1920 size (dp) 411x731 resolution 1080x1920 size (dp) 360x640
Google Pixel Nexus 5 resolution 1080x1920 size (dp) 411x731 resolution
1080x1920 size (dp) 360x640
Wide range of Android devices with different screen resolutions and
densities
<View android:layout_width=“300dp” android:layout_height=“100dp” />
/res/values-sw411dp/dimens.xml /res/layout-sw411dp <View android:layout_width=“@dimen/block_width” android:layout_height=“100dp” /> sw?
topView centerTopView centerBottomView bottomView val screenWidth = resources.displayMetrics.widthPixels val topW
= screenWidth * ( 300 / 360f ) val cTopW = screenWidth * ( 250 / 360f ) val cBottomW = screenWidth * ( 200 / 360f ) val bottomW = screenWidth * ( 150 / 360f ) val height = (100 * resources.displayMetrics.density) default view width default screen width
topView centerTopView centerBottomView bottomView topView.layoutParams = LinearLayout.LayoutParams(topW, height) centerTopView.layoutParams =
LinearLayout.LayoutParams(cTopW, height) centerBottomView.layoutParams = LinearLayout.LayoutParams(cBottomW, height) bottomView.layoutParams = LinearLayout.LayoutParams(bottomW, height)
Design for 1080x1920 480dpi (xxhdpi) take into account different screens
width (do the math) https://material.io/devices/ Don’t trust the Android emulator Google Pixel has 480 DPI instead of 416 DPI
Custom Views
class CustomView : View { } ViewGroup FrameLayout …
class CustomView : View { constructor(context: Context) : super(context) constructor(context:
Context, attrs: AttributeSet) : super(context, attrs) }
class CustomView : View { override fun onAttachedToWindow() override fun
onDetachedFromWindow() }
onMeasure() onLayout() onDraw() onAttachedToWindow() onDetachedFromWindow()
onDraw()
onDraw()
Canvas()
Canvas() Y X resources.displayMetrics .widthPixels resources.displayMetrics .heightPixels
Canvas() Y X resources.displayMetrics .widthPixels resources.displayMetrics .heightPixels Paint() - geometries
- text - bitmaps Path()
resources.displayMetrics.widthPixels ? ? ? ? ?
? ? 70dp ? ? resources.displayMetrics.widthPixels
? ? ? 70dp 50dp resources.displayMetrics.widthPixels
? 110dp 20dp 70dp 50dp resources.displayMetrics.widthPixels
110dp 20dp 70dp 50dp resources.displayMetrics.widthPixels resources.displayMetrics.widthPixels-130dp
110dp 20dp 70dp 50dp resources.displayMetrics.widthPixels resources.displayMetrics.widthPixels-130dp
path.lineTo(0f, containerHeight) (0, 0) (0, containerHeight)
path.lineTo(screenWidthPx - tagWidthBigPx, containerHeight)
path.cubicTo( screenWidthPx - tagWidthSmallPx, containerHeight, screenWidthPx - tagWidthBigPx, containerHeight +
tagHeightPx, screenWidthPx - tagWidthSmallPx, containerHeight + tagHeightPx )
path.cubicTo( screenWidthPx - tagWidthSmallPx, containerHeight, screenWidthPx - tagWidthBigPx, containerHeight +
tagHeightPx, screenWidthPx - tagWidthSmallPx, containerHeight + tagHeightPx ) http://cubic-bezier.com/
x path.cubicTo( screenWidthPx - tagWidthSmallPx, containerHeight, screenWidthPx - tagWidthBigPx, containerHeight
+ tagHeightPx, screenWidthPx - tagWidthSmallPx, containerHeight + tagHeightPx )
path.cubicTo( screenWidthPx - tagWidthSmallPx, containerHeight, screenWidthPx - tagWidthBigPx, containerHeight +
tagHeightPx, screenWidthPx - tagWidthSmallPx, containerHeight + tagHeightPx ) x
path.cubicTo( screenWidthPx - tagWidthSmallPx, containerHeight, screenWidthPx - tagWidthBigPx, containerHeight +
tagHeightPx, screenWidthPx - tagWidthSmallPx, containerHeight + tagHeightPx ) x
path.lineTo(screenWidthPx, containerHeight + tagHeightPx)
path.lineTo(screenWidthPx, 0f)
path.lineTo(0f, 0f)
private val path by lazy { Path() } override fun
onDraw(canvas: Canvas) { super.onDraw(canvas) with(path) { lineTo(…) cubicTo(…) … } canvas.drawPath(path, paint) }
private val paint by lazy { Paint().apply { style =
Paint.Style.FILL color = Color.WHITE isAntiAlias = true } }
onMeasure() onLayout() onDraw() onAttachedToWindow() onDetachedFromWindow()
None
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/blue"> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.example.CustomView android:layout_width="match_parent" android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|bottom" android:layout_margin="12dp" android:text="123" android:textSize="30sp"/> </FrameLayout> </FrameLayout>
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/blue"> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.example.CustomView android:layout_width="match_parent" android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|bottom" android:layout_margin="12dp" android:text="123" android:textSize="30sp"/> </FrameLayout> </FrameLayout>
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val customHeightMeasuredSpec
= MeasureSpec.makeMeasureSpec( containerHeight + tagHeightPx, MeasureSpec.EXACTLY) super.onMeasure(widthMeasureSpec, customHeightMeasuredSpec) } setMeasuredDimension(measuredWidth: Int, measuredHeight: Int)
onMeasure() onLayout() onDraw() onAttachedToWindow() onDetachedFromWindow() invalidate()
None
BL BR TR TL
BL BR TR TL
BL BR TR TL
BL BR TR TL
BL BR TR TL AL AR
BL BR TR TL AL AR
BL BR TR TL AL AR
TL width center in x axis = width / 2
TLx = width / 2 - peekTopWidth / 2 TLy
= 0f TL width center in x axis = width / 2 peekTopWidth y x
BLx = (width / 2 - peekBottomWidth / 2) *
(1 - offset * 3) BL x offset = 0,1 BLx = 100 * (1 - 0,1 * 3) = 100 * 0,7 = 70 offset = 0,2 BLx = 100 * (1 - 0,2 * 3) = 100 * 0,4 = 40
BL y peekHeight BLy = if(BLx >= 0) { peekHeight
} else { ?????????? }
BL y BLy = if(BLx >= 0) { peekHeight }
else { ?????????? } peekHeight
peekHeight BLy = peekHeight - (peekHeight * offset) offset =
0,1 BLy = 100 - (100 * 0,1) = 100 - 10 = 90 offset = 0,6 BLy = 100 - (100 * 0,6) = 100 - 60 = 40
peekHeight BLy = peekHeight - (peekHeight * offset) offset =
0,1 BLy = 100 - (100 * 0,1) = 100 - 10 = 90 offset = 0,35 BLy = 100 - (100 * 0,35) = 100 - 35 = 65 offset is 0,35!!!
x x (anchorOffset, 0) (1, 1) offset = 0,35 peekHeight
peekHeight - (peekHeight * offset)
x x (anchorOffset, 0) (1, 1) To calculate the slope
of a line you need only two points from that line m = ( y2 - y1 ) / ( x2 - x1 )
x x (anchorOffset, 0) (1, 1) m = ( 1
- 0 ) / ( 1 - anchorOffset )
x x (anchorOffset, 0) (1, 1) Equation of a line
given a point and the slope y - y1 = m ( x - x1 )
x x (anchorOffset, 0) (1, 1) y - 0 =
( 1 / (1 - anchorOffset ) ) * ( x - anchorOffset )
x x (anchorOffset, 0) (1, 1) y = ( x
- anchorOffset ) / ( 1 - anchorOffset )
BLy = if(BLx >= 0) { peekHeight } else
{ if(anchorOffset = 0) { anchorOffset = offset } verticalOffset = (offset - anchorOffset) / (1 - anchorOffset) peekHeight - (peekHeight * verticalOffset) } BL y y = ( x - anchorOffset ) / ( 1 - anchorOffset )
BL BR TR TL TRx = width - TLx TRy
= TLy BRx = width - BLx BRy = BLy
ALx = BLx ALy = peekHeight ARx = BRx ARy
= peekHeight BL BR TR TL AL AR
override fun onPanelSlide(slideOffset: Float) { offset = slideOffset invalidate() }
private fun init() { setWillNotDraw(false) LayoutInflater.from(context).inflate(xml, this, true) invalidate() }
Be careful with onDraw() don’t call methods that call onDraw()
or requestLayout()
Master your custom views #19231A DARK JUNGLE GREEN @rmgoncalo Rui
Gonçalo