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
150
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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
More Decks by Rui Gonçalo
See All by Rui Gonçalo
NYC Android meetup May 2019
ruigoncalo
0
38
Start speking it @ Droidcon Krakow 2017
ruigoncalo
0
120
Espressing yourself
ruigoncalo
0
74
Other Decks in Programming
See All in Programming
新卒PdEのリアル
ryu1013
1
420
LLMは4年分のCompose移行を再現できるのか?実プロダクト279件のXMLで探る自動化の境界線
makun
0
440
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
100
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
3
1.5k
AIを上手に使っていこうとしたら越境せざるを得なくなった話 〜実践1年で見えた境界を越えなければならない理由と進め方〜 / Crossing borders with AI
tomoyakitaura
3
1.1k
AIの中の人になってみる
htkym
0
170
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
6
2.5k
From 6 People Classroom Meetup to 100 People Regional Conference / FOSS4G Hiroshima 2026
furukawayasuto
0
110
PyO3 で既存 Python 評価器を Rust core 化する ー wasm-bindgen でブラウザにも配るための設計
kdash
1
480
Kiroで創り、AgentCoreで繋ぐ!AWSで実践する「AI-DLC」から「AIエージェント統合」までの最新地図
licux
2
350
スマート反転とウェブアクセシビリティ
camiha
0
120
Can LLMs Replicate 4 Years of Compose Migration? Exploring the boundaries of automation with 279 XML files from a real product
makun
0
130
Featured
See All Featured
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.6k
Building Applications with DynamoDB
mza
96
7.2k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
490
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
310
Into the Great Unknown - MozCon
thekraken
41
2.7k
Code Review Best Practice
trishagee
74
20k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
370
Become a Pro
speakerdeck
PRO
31
6.2k
A Soul's Torment
seathinner
7
3.6k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3k
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