提交 57df667f 编写于 作者: A Adam Barth

Add a basic InkWell implementation

This CL replaces the (non-working) components2 InkWell with some code based on
the ink_well example. There are at least two issues with the implementation:

1) The ink splash always starts at the center of the well because we don't have
   a facility for converting from global to local coordinates, which means we
   can't tell where the tap occurred in the local coordinates we need to use
   for painting.

2) When used inside a MenuItem, the in splash disappears shortly after
   starting, presumably because the button starts highlighting, which causes a
   component rebuild and somehow we lose the RenderInkWell instance.

I plan to address these issues in subsequent CLs.

R=ianh@google.com

Review URL: https://codereview.chromium.org/1172033003.
上级 52fd41bc
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:sky' as sky;
import 'package:sky/framework/app.dart';
import 'package:sky/framework/rendering/box.dart';
import 'package:sky/framework/rendering/object.dart';
import 'package:sky/framework/animation/animated_value.dart';
import 'package:sky/framework/animation/curves.dart';
const double _kInitialSize = 0.0;
const double _kTargetSize = 100.0;
const double _kSplashDuration = 500.0;
const int _kInitialOpacity = 0x80;
class InkSplash {
final InkWell inkWell;
final sky.Paint _paint = new sky.Paint();
final sky.Point position;
AnimatedValue radius;
InkSplash({ this.position, this.inkWell }) {
radius = new AnimatedValue(_kInitialSize, onChange: _handleRadiusChange);
radius.animateTo(_kTargetSize, _kSplashDuration, curve: easeOut);
}
void _handleRadiusChange() {
if (radius.value == _kTargetSize)
inkWell._splashes.remove(this);
inkWell.markNeedsPaint();
}
void paint(RenderObjectDisplayList canvas) {
int opacity = (_kInitialOpacity * (1.0 - (radius.value / _kTargetSize))).floor();
_paint.color = new sky.Color(opacity << 24);
canvas.drawCircle(position.x, position.y, radius.value, _paint);
}
}
class InkWell extends RenderBox {
final List<InkSplash> _splashes = new List<InkSplash>();
void handleEvent(sky.Event event) {
switch (event.type) {
case 'pointerdown':
_splashes.add(new InkSplash(position: new sky.Point(event.x, event.y),
inkWell: this));
break;
}
markNeedsPaint();
}
void performLayout() {
size = constraints.constrain(sky.Size.infinite);
}
void paint(RenderObjectDisplayList canvas) {
canvas.drawRect(new sky.Rect.fromLTRB(0.0, 0.0, size.width, size.height),
new sky.Paint()..color = const sky.Color(0xFFCCCCCC));
for (InkSplash splash in _splashes)
splash.paint(canvas);
}
}
AppView app;
void main() {
app = new AppView(new InkWell());
}
...@@ -53,7 +53,6 @@ dart_pkg("sdk") { ...@@ -53,7 +53,6 @@ dart_pkg("sdk") {
"lib/framework/components2/floating_action_button.dart", "lib/framework/components2/floating_action_button.dart",
"lib/framework/components2/icon.dart", "lib/framework/components2/icon.dart",
"lib/framework/components2/icon_button.dart", "lib/framework/components2/icon_button.dart",
"lib/framework/components2/ink_splash.dart",
"lib/framework/components2/ink_well.dart", "lib/framework/components2/ink_well.dart",
"lib/framework/components2/input.dart", "lib/framework/components2/input.dart",
"lib/framework/components2/material.dart", "lib/framework/components2/material.dart",
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../animation/animated_value.dart';
import '../animation/curves.dart';
import '../fn2.dart';
import '../theme/view_configuration.dart' as config;
import 'dart:async';
import 'dart:math' as math;
const double _kSplashConfirmedDuration = 350.0;
const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout;
const double _kSplashAbortDuration = 100.0;
const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls
double _getSplashTargetSize(Rect rect, double x, double y) {
return 2.0 * math.max(math.max(x - rect.x, rect.x + rect.width - x),
math.max(y - rect.y, rect.y + rect.height - y));
}
class SplashController {
SplashController(Rect rect, double x, double y,
{ this.pointer, Function onDone })
: _offsetX = x - rect.x,
_offsetY = y - rect.y,
_targetSize = _getSplashTargetSize(rect, x, y) {
_styleStream = _size.onValueChanged.map((p) {
if (p == _targetSize) {
onDone();
}
double size;
if (_growing) {
size = p;
_lastSize = p;
} else {
size = _lastSize;
}
return '''
top: ${_offsetY - size/2}px;
left: ${_offsetX - size/2}px;
width: ${size}px;
height: ${size}px;
border-radius: ${size}px;
opacity: ${1.0 - (p / _targetSize)};''';
});
start();
}
final int pointer;
Stream<String> get onStyleChanged => _styleStream;
final AnimatedValue _size = new AnimatedValue(0.0);
double _offsetX;
double _offsetY;
double _lastSize = 0.0;
bool _growing = true;
double _targetSize;
Stream<String> _styleStream;
void start() {
_size.animateTo(_targetSize, _kSplashUnconfirmedDuration, curve: easeOut, initialDelay: _kSplashInitialDelay);
}
void confirm() {
double fractionRemaining = (_targetSize - _size.value) / _targetSize;
double duration = fractionRemaining * _kSplashConfirmedDuration;
if (duration <= 0.0)
return;
_size.animateTo(_targetSize, duration, curve: easeOut);
}
void abort() {
_growing = false;
double durationRemaining = _size.remainingTime;
if (durationRemaining <= _kSplashAbortDuration)
return;
_size.animateTo(_targetSize, _kSplashAbortDuration, curve: easeOut);
}
void cancel() {
_size.stop();
}
}
class InkSplash extends Component {
InkSplash(Stream<String> onStyleChanged)
: onStyleChanged = onStyleChanged,
super(stateful: true, key: onStyleChanged.hashCode);
static final Style _clipperStyle = new Style('''
position: absolute;
pointer-events: none;
overflow: hidden;
top: 0;
left: 0;
bottom: 0;
right: 0;''');
static final Style _splashStyle = new Style('''
position: absolute;
background-color: rgba(0, 0, 0, 0.2);''');
Stream<String> onStyleChanged;
double _offsetX;
double _offsetY;
String _inlineStyle;
bool _listening = false;
void _ensureListening() {
if (_listening)
return;
_listening = true;
onStyleChanged.listen((style) {
setState(() {
_inlineStyle = style;
});
});
}
UINode build() {
_ensureListening();
return new Container(
style: _clipperStyle,
children: [
new Container(
inlineStyle: _inlineStyle,
style: _splashStyle
)
]
);
}
}
...@@ -2,125 +2,126 @@ ...@@ -2,125 +2,126 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import '../animation/animated_value.dart';
import '../animation/curves.dart';
import '../fn2.dart'; import '../fn2.dart';
import '../rendering/box.dart';
import '../rendering/flex.dart'; import '../rendering/flex.dart';
import '../rendering/object.dart';
import '../theme/view_configuration.dart' as config;
import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:math' as math;
import 'dart:sky' as sky; import 'dart:sky' as sky;
// import 'ink_splash.dart';
import 'scrollable.dart';
class InkWell extends Component implements ScrollClient { const int _kSplashInitialOpacity = 0x80;
const double _kSplashInitialSize = 0.0;
const double _kSplashConfirmedDuration = 350.0;
const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout;
const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls
InkWell({ Object key, this.children }) : super(key: key); double _getSplashTargetSize(Size bounds, Point position) {
return 2.0 * math.max(math.max(position.x, bounds.width - position.x),
math.max(position.y, bounds.height - position.y));
}
// static final Style _containmentStyleHack = new Style(''' class InkSplash {
// align-items: center; InkSplash(this.pointer, this.position, this.well) {
// transform: translateX(0);'''); _targetRadius = _getSplashTargetSize(well.size, position);
_radius = new AnimatedValue(_kSplashInitialSize, onChange: _handleRadiusChange);
_radius.animateTo(_targetRadius, _kSplashUnconfirmedDuration,
curve: easeOut, initialDelay: _kSplashInitialDelay);
}
// LinkedHashSet<SplashController> _splashes; final int pointer;
final Point position;
final RenderInkWell well;
List<UINode> children; double _targetRadius;
AnimatedValue _radius;
// InkWell({ Object key, this.inlineStyle, this.children }) void confirm() {
// : super(key: key) { double fractionRemaining = (_targetRadius - _radius.value) / _targetRadius;
// onDidUnmount(() { double duration = fractionRemaining * _kSplashConfirmedDuration;
// _cancelSplashes(null); if (duration <= 0.0)
// }); return;
// } _radius.animateTo(_targetRadius, duration, curve: easeOut);
}
UINode build() { void _handleRadiusChange() {
return new FlexContainer( if (_radius.value == _targetRadius)
direction: FlexDirection.horizontal, well._splashes.remove(this);
justifyContent: FlexJustifyContent.center, well.markNeedsPaint();
children: children);
// List<UINode> childrenIncludingSplashes = [];
// if (_splashes != null) {
// childrenIncludingSplashes.addAll(
// _splashes.map((s) => new InkSplash(s.onStyleChanged)));
// }
// if (children != null)
// childrenIncludingSplashes.addAll(children);
// return new EventListenerNode(
// new FlexContainer(
// direction: FlexDirection.horizontal,
// style: _containmentStyleHack,
// inlineStyle: inlineStyle,
// children: childrenIncludingSplashes),
// onGestureTapDown: _startSplash,
// onGestureTap: _confirmSplash
// );
} }
// void _startSplash(sky.GestureEvent event) { void paint(RenderObjectDisplayList canvas) {
// setState(() { int opacity = (_kSplashInitialOpacity * (1.0 - (_radius.value / _targetRadius))).floor();
// if (_splashes == null) sky.Paint paint = new sky.Paint()..color = new sky.Color(opacity << 24);
// _splashes = new LinkedHashSet<SplashController>(); canvas.drawCircle(position.x, position.y, _radius.value, paint);
// var splash;
// var root = getRoot();
// splash = new SplashController(root.rect, event.x, event.y,
// pointer: event.primaryPointer,
// onDone: () { _splashDone(splash); });
// _splashes.add(splash);
// UINode node = parent;
// while (node != null) {
// if (node is Scrollable)
// node.registerScrollClient(this);
// node = node.parent;
// }
// });
// }
bool ancestorScrolled(Scrollable ancestor) {
// _abortSplashes();
return false;
} }
}
// void handleRemoved() { class RenderInkWell extends RenderProxyBox {
// UINode node = parent; RenderInkWell({ RenderBox child }) : super(child);
// while (node != null) {
// if (node is Scrollable) final List<InkSplash> _splashes = new List<InkSplash>();
// node.unregisterScrollClient(this);
// node = node.parent; void handleEvent(sky.Event event) {
// } switch (event.type) {
// super.handleRemoved(); case 'gesturetapdown':
// } // TODO(abarth): We should position the splash at the location of the tap.
_startSplash(event.primaryPointer, new Point(size.width / 2.0, size.height / 2.0));
// void _confirmSplash(sky.GestureEvent event) { break;
// if (_splashes == null) case 'gesturetap':
// return; _confirmSplash(event.primaryPointer);
// _splashes.where((splash) => splash.pointer == event.primaryPointer) break;
// .forEach((splash) { splash.confirm(); }); }
// } }
// void _abortSplashes() { void _startSplash(int pointer, Point position) {
// if (_splashes == null) _splashes.add(new InkSplash(pointer, position, this));
// return; markNeedsPaint();
// setState(() { }
// _splashes.forEach((s) { s.abort(); });
// });
// }
// void _cancelSplashes(sky.Event event) {
// if (_splashes == null)
// return;
// setState(() {
// var splashes = _splashes;
// _splashes = null;
// splashes.forEach((s) { s.cancel(); });
// });
// }
// void _splashDone(SplashController splash) {
// if (_splashes == null)
// return;
// setState(() {
// _splashes.remove(splash);
// if (_splashes.length == 0)
// _splashes = null;
// });
// }
void _confirmSplash(int pointer) {
_splashes.where((splash) => splash.pointer == pointer)
.forEach((splash) { splash.confirm(); });
markNeedsPaint();
}
void paint(RenderObjectDisplayList canvas) {
if (!_splashes.isEmpty) {
canvas.save();
canvas.clipRect(new Rect.fromSize(size));
for (InkSplash splash in _splashes)
splash.paint(canvas);
canvas.restore();
}
super.paint(canvas);
}
}
class InkWellWrapper extends OneChildRenderObjectWrapper {
InkWellWrapper({ UINode child, Object key })
: super(child: child, key: key);
RenderInkWell root;
RenderInkWell createNode() => new RenderInkWell();
}
class InkWell extends Component {
InkWell({ Object key, this.children }) : super(key: key);
List<UINode> children;
UINode build() {
return new InkWellWrapper(
child: new FlexContainer(
direction: FlexDirection.horizontal,
justifyContent: FlexJustifyContent.center,
children: children
)
);
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册