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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Rui Gonçalo
October 18, 2017
Programming
140
1
Share
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
35
Start speking it @ Droidcon Krakow 2017
ruigoncalo
0
120
Espressing yourself
ruigoncalo
0
72
Other Decks in Programming
See All in Programming
L’IA au service des devs : Anatomie d'un assistant de Code Review
toham
0
210
PDI: Como Alavancar Sua Carreira e Seu Negócio
marcelgsantos
0
100
車輪の再発明をしよう!PHP で実装して学ぶ、Web サーバーの仕組みと HTTP の正体
h1r0
3
510
Symfony + NelmioApiDocBundle を使った スキーマ駆動開発 / Schema Driven Development with NelmioApiDocBundle
okashoi
0
270
「接続」—パフォーマンスチューニングの最後の一手 〜点と点を結ぶ、その一瞬のために〜
kentaroutakeda
5
2.5k
Java 21/25 Virtual Threads 소개
debop
0
330
ネイティブアプリとWebフロントエンドのAPI通信ラッパーにおける共通化の勘所
suguruooki
0
250
条件判定に名前、つけてますか? #phperkaigi #c
77web
2
970
今からFlash開発できるわけないじゃん、ムリムリ! (※ムリじゃなかった!?)
arkw
0
190
Coding as Prompting Since 2025
ragingwind
0
760
PHP 7.4でもOpenTelemetryゼロコード計装がしたい! / PHPerKaigi 2026
arthur1
1
530
Migration to Signals, Signal Forms, Resource API, and NgRx Signal Store @Angular Days 03/2026 Munich
manfredsteyer
PRO
0
240
Featured
See All Featured
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
150
Site-Speed That Sticks
csswizardry
13
1.1k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
250
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
110k
Become a Pro
speakerdeck
PRO
31
5.9k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.7k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
1
170
Side Projects
sachag
455
43k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
300
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
160
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.1k
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