I really interested with flutter, and currently I'm trying to create a custom clipper with clipPart widget. but it's seems not working correctly.
here is my code
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title: "My App",
home: HomePage(),
debugShowCheckedModeBanner: false,
));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
alignment: Alignment.topCenter,
color: Colors.teal,
child: HomeScreenTop(),
)
],
),
);
}
}
class HomeScreenTop extends StatefulWidget {
#override
_HomeScreenTopState createState() => _HomeScreenTopState();
}
class _HomeScreenTopState extends State<HomeScreenTop> {
#override
Widget build(BuildContext context) {
return Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: WaveContainer(),
),
],
);
}
}
class WaveContainer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ClipPath(
clipper: CustomShapeClipper(),
clipBehavior: Clip.antiAlias,
child: Container(
width: MediaQuery.of(context).size.width,
height: 400.0,
color: Colors.orange,
),
);
}
}
class CustomShapeClipper extends CustomClipper<Path> {
#override
getClip(Size size) {
print(size.height);
final Path path =Path();
path.lineTo(size.width / 2, size.height);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) => true;
}
what I want to do is, create a triangle from top to bottom, as I know from the tutorial, Offset of x = 0 and y = 0 should be pointing to left-top corner of the page. So if create like this
path.lineTo(size.width / 2, size.height);
path.lineTo(size.width, 0);
the path should be drawing from left-top to middle-down to right-top and back to left-top
But what I've got when I try the code is like this
And, the stack is not working.. the triangle should isolated inside the container but why the custom clipper can exceed the bottom container??
I really confuse, Please help
Solved by change the emulator from Genymotion to iOS IphoneX emulator. Don't know why it show different behaviour between iOS and android, but I think iOS emulator is the right one..
Related
I'm new to Flutter, I have a requirement where I need to place a RaisedButton at the edge of an image as shown in the below screenshot(because of privacy, I covered the content on the mockup)
I tried by changing the padding but it's not working with all the devices in iOS and Android. Please help me out to achieve this for all kind of devices present in iOS and Android.
class SO extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.orange.shade200,
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image.asset('assets/images/pngs/cake.png'),
SizedBox(
height: 25,
)
],
),
RaisedButton(
onPressed: () {},
child: Text("sample button"),
),
],
),
);
}
}
gives
EDIT: One of the non-hacky ways to do this.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
final sm = 100.0, lg = 200.0;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
width: lg,
height: lg,
alignment: FractionalOffset.bottomCenter +
FractionalOffset.fromOffsetAndSize(
Offset(0, sm / 2),
Size(sm, sm),
),
child: Container(
color: Colors.blue,
width: sm,
height: sm,
),
);
}
}
Did you try Stack in Flutter ?
I am using RepaintBoundary to take the screenshot of the current widget which is a listView. But it only captures the content which is visible on the screen at the time.
RepaintBoundary(
key: src,
child: ListView(padding: EdgeInsets.only(left: 10.0),
scrollDirection: Axis.horizontal,
children: <Widget>[
Align(
alignment: Alignment(-0.8, -0.2),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: listLabel(orientation),
)
),
Padding(padding: EdgeInsets.all(5.0)),
Align(
alignment: FractionalOffset(0.3, 0.5),
child: Container(
height: orientation == Orientation.portrait? 430.0: 430.0*0.7,
decoration: BoxDecoration(
border: Border(left: BorderSide(color: Colors.black))
),
//width: 300.0,
child:
Wrap(
direction: Axis.vertical,
//runSpacing: 10.0,
children: colWidget(orientation),
)
)
),
Padding(padding: EdgeInsets.all(5.0)),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: listLabel(orientation),
)
],
),
);
screenshot function:
Future screenshot() async {
RenderRepaintBoundary boundary = src.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
print(pngBytes);
final directory = (await getExternalStorageDirectory()).path;
File imgFile =new File('$directory/layout2.pdf');
imgFile.writeAsBytes(pngBytes);
}
Is there any way, so that I can capture the whole listView, i.e., not only the content which is not visible on the screen but the scrollable content also. Or maybe if the whole widget is too large to fit in a picture, it can be captured in multiple images.
I achieve the solution of this problem using this package: Screenshot, that takes a screenshot of the entire widget. It's easy and simple, follow the steps on the PubDev or GitHub and you can make it work.
OBS: To take a full screenshot of the widget make sure that your widget is fully scrollable, and not just a part of it.
(In my case, i had a ListView inside a Container, and the package doesn't take the screenshot of all ListView because i have many itens on it, SO i have wrap my Container inside a SingleChildScrollView and add the NeverScrollableScrollPhysics physics in the ListView and it works! :D).
Screenshot of my screen
More details in this issue
This made me curious whether it was possible so I made a quick mock-up that shows it does work. But please be aware that by doing this you're essentially intentionally breaking the things flutter does to optimize, so you really shouldn't use it beyond where you absolutely have to.
Anyways, here's the code:
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class UiImagePainter extends CustomPainter {
final ui.Image image;
UiImagePainter(this.image);
#override
void paint(ui.Canvas canvas, ui.Size size) {
// simple aspect fit for the image
var hr = size.height / image.height;
var wr = size.width / image.width;
double ratio;
double translateX;
double translateY;
if (hr < wr) {
ratio = hr;
translateX = (size.width - (ratio * image.width)) / 2;
translateY = 0.0;
} else {
ratio = wr;
translateX = 0.0;
translateY = (size.height - (ratio * image.height)) / 2;
}
canvas.translate(translateX, translateY);
canvas.scale(ratio, ratio);
canvas.drawImage(image, new Offset(0.0, 0.0), new Paint());
}
#override
bool shouldRepaint(UiImagePainter other) {
return other.image != image;
}
}
class UiImageDrawer extends StatelessWidget {
final ui.Image image;
const UiImageDrawer({Key key, this.image}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomPaint(
size: Size.infinite,
painter: UiImagePainter(image),
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
GlobalKey<OverRepaintBoundaryState> globalKey = GlobalKey();
ui.Image image;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: image == null
? Capturer(
overRepaintKey: globalKey,
)
: UiImageDrawer(image: image),
floatingActionButton: image == null
? FloatingActionButton(
child: Icon(Icons.camera),
onPressed: () async {
var renderObject = globalKey.currentContext.findRenderObject();
RenderRepaintBoundary boundary = renderObject;
ui.Image captureImage = await boundary.toImage();
setState(() => image = captureImage);
},
)
: FloatingActionButton(
onPressed: () => setState(() => image = null),
child: Icon(Icons.remove),
),
),
);
}
}
class Capturer extends StatelessWidget {
static final Random random = Random();
final GlobalKey<OverRepaintBoundaryState> overRepaintKey;
const Capturer({Key key, this.overRepaintKey}) : super(key: key);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: OverRepaintBoundary(
key: overRepaintKey,
child: RepaintBoundary(
child: Column(
children: List.generate(
30,
(i) => Container(
color: Color.fromRGBO(random.nextInt(256), random.nextInt(256), random.nextInt(256), 1.0),
height: 100,
),
),
),
),
),
);
}
}
class OverRepaintBoundary extends StatefulWidget {
final Widget child;
const OverRepaintBoundary({Key key, this.child}) : super(key: key);
#override
OverRepaintBoundaryState createState() => OverRepaintBoundaryState();
}
class OverRepaintBoundaryState extends State<OverRepaintBoundary> {
#override
Widget build(BuildContext context) {
return widget.child;
}
}
What it's doing is making a scroll view that encapsulates the list (column), and making sure the repaintBoundary is around the column. With your code where you use a list, there's no way it can ever capture all the children as the list is essentially a repaintBoundary in and of itself.
Note in particular the 'overRepaintKey' and OverRepaintBoundary. You might be able to get away without using it by iterating through render children, but it makes it a lot easier.
There is a simple way
You need wrap SingleChildScrollView Widget to RepaintBoundary. just wrap your Scrollable widget (or his father) with SingleChildScrollView
SingleChildScrollView(
child: RepaintBoundary(
key: _globalKey
)
)
I need to build a bottomNavigationBar with a vertical overflow TabBar item,
so I tried to use OverflowBox,it looks like useful.
but there is another problew,the overflowed part cannot respond to the button.
so what should I do make the GestureDetector effective?
or you have other ways to build a bottomNavigationBar like this?
thank you very much!
this is screen capture
this is main.dart:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'OverflowBox touch test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
List<Color> _colors = [
Colors.blue,
Colors.green,
Colors.yellow,
];
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{
String _tip = "";
TabController controller;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new TabBarView(
controller: controller,
children: <Widget>[
new Center(child: new Text("page 1")),
new Center(child: new Text("page 2")),
new Center(child: new Text("page 3")),
],
),
bottomNavigationBar: new Container(
height: 72.0,
alignment: Alignment.bottomCenter,
color: const Color.fromRGBO(45, 45, 45, 1.0),
child: new TabBar(
controller: controller,
indicatorWeight: 0.01,
tabs: <Widget>[
_getBarItem(0),
_getBarItem(1),
_getBarItem(2),
],
),
),
);
}
#override
void initState() {
super.initState();
controller = new TabController(length: 3, vsync: this);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget _getBarItem(int idx){
Widget ret = new Container(
width: 80.0,
height: idx==1?120.0:50.0,
color: _colors[idx],
);
if(idx==1){
ret = new OverflowBox(
maxHeight: double.infinity,
alignment: Alignment.bottomCenter,
child: new Container(
color: Colors.black,
child: new GestureDetector(
onTapDown: (x){
controller.animateTo(idx);
},
child: ret,
),
),
);
}
return ret;
}
}
Try to make your GestureDetector wrap your OverflowBox
Use this behavior on the Gesture Detector and then wrap your overflow with it.
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap(){..
)
You should use a Stack with a Positioned widget instead of an OverflowBox.
PS: Using functions that return widget should be avoided.
I can tell you the reason. OverflowBox widget layout is sizedByParent, so the size is 72. When hit test will ingore the pointer out the size. so the child widget overflowed part cannot respond to the button.
I have a Hero that has a sub-element I want to change size as part of the hero animation (in this case, I want its aspect ratio to change).
Here's my (stripped down) build method:
Widget build(BuildContext build) {
final aspect = expanded ? 1.3 : 1.0;
return new Hero(
child: Column(
children: <Widget>[
new AspectRatio(
aspect: aspect,
child: // Stuff that creates a header
),
new Container(
child: // General descriptions
)
],
);
}
Right now, the aspect ratio jumps between the two values. My question is, can I animate aspect along with the hero animation, and have it slowly shift from 1.3 to 1.0 and back?
For a complete example (which also shows a weird overflow issue during a hero animation) see this Gist:
https://gist.github.com/fuzzybinary/74196bccecc6dd070b35474c4c9223d7
Since I'm in my house right now I'm not able to try out your code right now. But I have a working hero animation example with me. Try this out.
// main.dart class
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter_test7/photo_hero.dart';
import 'package:flutter_test7/second_page.dart';
class HeroAnimation extends StatelessWidget {
Widget build(BuildContext context) {
timeDilation = 2.5; // 1.0 means normal animation speed.
return new Scaffold(
appBar: new AppBar(
title: const Text('Basic Hero Animation'),
),
body: new Center(
child: new PhotoHero(
photo: 'images/flippers-alpha.png',
width: 300.0,
onTap: () {
Navigator.of(context).pushNamed('/second_page');
},
),
),
);
}
}
void main() {
runApp(
new MaterialApp(
home: new HeroAnimation(),
routes: <String, WidgetBuilder>{
'/second_page': (context) => new SecondPage()
},
),
);
}
// photo_hero.dart class
import 'package:flutter/material.dart';
class PhotoHero extends StatelessWidget {
const PhotoHero({Key key, this.photo, this.onTap, this.width})
: super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
Widget build(BuildContext context) {
return new SizedBox(
width: width,
child: new Hero(
tag: photo,
child: new Material(
color: Colors.transparent,
child: new InkWell(
onTap: onTap,
child: new Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
);
}
}
// second_page.dart class
import 'package:flutter/material.dart';
import 'package:flutter_test7/photo_hero.dart';
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => new _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Flippers Page'),
),
body: new Container(
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: new PhotoHero(
photo: 'images/flippers-alpha.png',
width: 100.0,
onTap: () {
Navigator.of(context).pop();
},
),
),
);
}
}
Hope this helps.
I am trying to change the color of the element the user clicked on using a GestureDetector:
new GestureDetector(
onTap: (){
// Change the color of the container beneath
},
child: new Container(
width: 80.0,
height: 80.0,
margin: new EdgeInsets.all(10.0),
color: Colors.orange,
),
),
The problem is that I can't use setState inside of onTap. Otherwise I would have created a color variable. Any suggestions?
You can use setState() inside of onTap. In fact, that's exactly the right thing to do in this situation. If you are having trouble calling setState(), make sure your widget is stateful (see the interactivity tutorial).
You might also want to check out FlatButton or InkWell as more material-y ways to capture touches. If you really want a GestureDetector, read up on HitTestBehavior to make sure you're configuring it correctly.
Here's an example that changes to a random color every time it's clicked.
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHome(),
);
}
}
class MyHome extends StatefulWidget {
#override
State createState() => new _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
final Random _random = new Random();
Color _color = Colors.orange;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new GestureDetector(
onTap: () {
// Change the color of the container beneath
setState(() {
_color = new Color.fromRGBO(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
1.0
);
});
},
child: new Container(
width: 80.0,
height: 80.0,
margin: new EdgeInsets.all(10.0),
color: _color,
),
),
),
);
}
}